Multi-tasking prevents apps from freezing. In most programming languages, achieving this is a bit tricky, but the NSOperationQueue class in iOS makes it easy!

This tutorial will demonstrate how to use the NSOperationQueue class. An NSOperationQueue object is a queue that handles objects of the NSOperationclass type. An NSOperation object, simply phrased, represents a single task, including both the data and the code related to the task. The NSOperationQueue handles and manages the execution of all the NSOperation objects (the tasks) that have been added to it. The execution takes place with the main thread of the application. When an NSOperation object is added to the queue it is executed immediately and it does not leave the queue until it is finished. A task can be cancelled, but it is not removed from the queue until it is finished. The NSOperation class is an abstract one so it cannot be used directly in the program. Instead, there are two provided subclasses, the NSInvocationOperation class and theNSBlockOperation class. I'll use the first one in this tutorial.

The Sample Project

Here's the goal for this tutorial: for each extra thread we want our application to create an NSInvocationOperation (NSOperation) object. We'll add each object into the NSOperationQueue and then we're finished. The queue takes charge of everything and the app works without freezing. To demonstrate clearly the use of the classes I mentioned above, we will create a (simple) sample project in which, apart from the main thread of the app, we will have two more threads running along with it. On the first thread, a loop will run from 1 to 10,000,000 and every 100 steps a label will be updated with the loop's counter value. On the second thread, a label's background will fill with a custom color. That process will take place inside a loop and it will be executed more than once. So we will have something like a color rotator. At the same time, the RGB values of the custom background color along with the loop counter's value will be displayed next to the label. Finally, we will use three buttons to change the view's background color on the main thread. These tasks could not be executed simultaneously without multi-tasking. Here is a look at the end result:

Step 1: Create the Project

Let's begin by creating the project. Open the Xcode and create a new Single View Application.

Click on Next and set a name for the project. I named it ThreadingTestApp. You can use the same or any other name you like.

Next. complete the project creation.

Step 2: Setup the Interface

Click on the ViewController.xib file to reveal the Interface Builder. Add the following controls to create an interface like the next image:

  1. UINavigationBar

    • Frame (x, y, W, H): 0, 0, 320, 44
    • Tintcolor: Black color
    • Title: "Simple Multi-Threading Demo"
  2. UILabel
    • Frame (x, y, W, H): 20, 59, 280, 21
    • Text: "Counter at Thread #1"
  3. UILabel
    • Frame (x, y, W, H): 20, 88, 280, 50
    • Background color: Light gray color
    • Text color: Dark gray color
    • Text: -
  4. UILabel
    • Frame (x, y, W, H): 20, 154, 280, 21
    • Text: "Random Color Rotator at Thread #2"
  5. UILabel
    • Frame (x, y, W, H): 20, 183, 100, 80
    • Background color: Light gray color
    • Text: -
  6. UILabel
    • Frame (x, y, W, H): 128, 183, 150, 80
    • Text: -
  7. UILabel
    • Frame (x, y, W, H): 20, 374, 280, 21
    • Text: "Background Color at Main Thread"
  8. UIButton
    • Frame (x, y, W, H): 20, 403, 73, 37
    • Title: "Color #1"
  9. UIButton
    • Frame (x, y, W, H): 124, 403, 73, 37
    • Title: "Color #2"
  10. UIButton
    • Frame (x, y, W, H): 228, 403, 73, 37
    • Title: "Color #3"

For the last UILabel and the three UIButtons, set the Autosizing value to Left - Bottom to make the interface look nice on the iPhone 4/4S and iPhone 5, just like the next image:

Step 3: IBOutlet Properties and IBAction Methods

In this next step we will create the IBOutlet properties and IBAction methods that are necessary to make our sample app work. To create new properties and methods, and connect them to your controls while being the Interface Builder, click on the middle button of the Editor button at the Xcode toolbar to reveal the Assistant Editor:

Not every control needs an outlet property. We will add only one for the UILabels 3, 5, and 6 (according to the order they were listed in step 2), named label1, label2, and label3.

To insert a new outlet property, Control+Click (Right click) on a label > Click on the New Referencing Outlet > Drag and Drop into the Assistant Editor. After that, specify a name for the new property, just like in the following images:

Inserting a new IBOutlet property


Setting the IBOutlet property name

Repeat the process above three times to connect the three UILabels to properties. Inside your ViewController.h file you have these properties declared:

1
2
3
@property (retain, nonatomic) IBOutlet UILabel *label1;
@property (retain, nonatomic) IBOutlet UILabel *label2;
@property (retain, nonatomic) IBOutlet UILabel *label3;

Now add the IBAction methods for the three UIButtons. Each one button will change the background color of the view. To insert a new IBAction method, Control+Click (Right click) on a UIButton > Click on the Touch Up Inside > Drag and Drop into the Assistant Editor. After that specify a name for the new method. Take a look at the following images and the next snippet for the method names:


Inserting a new IBAction method

Setting the IBAction method name

Again, repeat the process above three times to connect every UIButton to an action method. The ViewController.h file should now contain these:

1
2
3
- (IBAction)applyBackgroundColor1;
- (IBAction)applyBackgroundColor2;
- (IBAction)applyBackgroundColor3;

The IBOutlet properties and IBAction methods are now ready. We can now begin coding.

Step 4: The NSOperationQueue Object and the Necessary Task-Related Method Declarations

One of the most important tasks we must do is to declare a NSOperationQueueobject (our operation queue), which will be used to execute our tasks in secondary threads. Open the ViewController.h file and add the following content right after the @interface header (don't forget the curly brackets):

1
2
3
@interface ViewController : UIViewController{
    NSOperationQueue *operationQueue;
}

Also, each task needs to have at least one method which contains the code that will run simultaneously with the main thread. According to the introductory description, the first task the method will be named counterTask and the second one will be named colorRotatorTask:

1
2
-(void)counterTask;
-(void)colorRotatorTask;

That's all we need. Our ViewController.h file should look like this:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@interface ViewController : UIViewController{
    NSOperationQueue *operationQueue;
}
 
@property (retain, nonatomic) IBOutlet UILabel *label1;
@property (retain, nonatomic) IBOutlet UILabel *label2;
@property (retain, nonatomic) IBOutlet UILabel *label3;
 
- (IBAction)applyBackgroundColor1;
- (IBAction)applyBackgroundColor2;
- (IBAction)applyBackgroundColor3;
 
-(void)counterTask;
-(void)colorRotatorTask;
 
@end

Let's move on to implementation.

Step 5: Implementation

We're almost finished. We have setup our interface, made all the necessary connections, declared any needed IBAction and other methods, and established our base. Now it is time to build upon them.

Open the ViewController.m file and go to the viewDidLoad method. The most important part of this tutorial is going to take place here. We will create a newNSOperationQueue instance and two NSOperation (NSInvocationOperation)objects. These objects will encapsulate the code of the two methods we previously declared and then they will be executed on their own by the NSOperationQueue. Here is the code:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Create a new NSOperationQueue instance.
    operationQueue = [NSOperationQueue new];
 
    // Create a new NSOperation object using the NSInvocationOperation subclass.
    // Tell it to run the counterTask method.
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                selector:@selector(counterTask)
                                                                object:nil];
    // Add the operation to the queue and let it to be executed.
    [operationQueue addOperation:operation];
    [operation release];
 
    // The same story as above, just tell here to execute the colorRotatorTask method.
    operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                    selector:@selector(colorRotatorTask)
                                                    object:nil];
    [operationQueue addOperation:operation];
    [operation release];
}

This whole process is really simple. After creating the NSOperationQueue instance, we create an NSInvocationOperation object (operation). We set its selector method (the code we want executed on a separate thread), and then we add it to the queue. Once it enters the queue it immediately begins to run. After that the operation object can be released, since the queue is responsible for handling it from now on. In this case we create another object and we'll use it the same way for the second task (colorRotatorTask).

Our next task is to implement the two selector methods. Let's begin by writing thecounterTask method. It will contain a for loop that will run for a large number of iterations and every 100 steps the label1's text will be updated with the current iteration's counter value (i). The code is simple, so here is everything:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
-(void)counterTask{
    // Make a BIG loop and every 100 steps let it update the label1 UILabel with the counter's value.
    for (int i=0; i<10000000; i++) {
        if (i % 100 == 0) {
            // Notice that we use the performSelectorOnMainThread method here instead of setting the label's value directly.
            // We do that to let the main thread to take care of showing the text on the label
            // and to avoid display problems due to the loop speed.
            [label1 performSelectorOnMainThread:@selector(setText:)
                                        withObject:[NSString stringWithFormat:@"%d", i]
                                        waitUntilDone:YES];
        }
    }
 
    // When the loop gets finished then just display a message.
    [label1 performSelectorOnMainThread:@selector(setText:) withObject:@"Thread #1 has finished." waitUntilDone:NO];
}

Please note that it is recommended as the best practice (even by Apple) to perform any visual updates on the interface using the main thread and not by doing it directly from a secondary thread. Therefore, the use of the performSelectorOnMainThreadmethod is necessary in cases such as this one.

Now let's implement the colorRotatorTask method:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    -(void)colorRotatorTask{
    // We need a custom color to work with.
    UIColor *customColor;
 
    // Run a loop with 500 iterations.
    for (int i=0; i<500; i++) {
        // Create three float random numbers with values from 0.0 to 1.0.
        float redColorValue = (arc4random() % 100) * 1.0 / 100;
        float greenColorValue = (arc4random() % 100) * 1.0 / 100;
        float blueColorValue = (arc4random() % 100) * 1.0 / 100;
 
        // Create our custom color. Keep the alpha value to 1.0.
        customColor = [UIColor colorWithRed:redColorValue green:greenColorValue blue:blueColorValue alpha:1.0];
 
        // Change the label2 UILabel's background color.
        [label2 performSelectorOnMainThread:@selector(setBackgroundColor:) withObject:customColor waitUntilDone:YES];
 
        // Set the r, g, b and iteration number values on label3.
        [label3 performSelectorOnMainThread:@selector(setText:)
                                    withObject:[NSString stringWithFormat:@"Red: %.2f\nGreen: %.2f\nBlue: %.2f\Iteration #: %d", redColorValue, greenColorValue, blueColorValue, i]
                                    waitUntilDone:YES];
 
        // Put the thread to sleep for a while to let us see the color rotation easily.
        [NSThread sleepForTimeInterval:0.4];
    }
 
    // Show a message when the loop is over.
    [label3 performSelectorOnMainThread:@selector(setText:) withObject:@"Thread #2 has finished." waitUntilDone:NO];
}

You can see that we used the performSelectorOnMainThread method here as well. The next step is the [NSThread sleepForTimeInterval:0.4]; command, which is used to cause some brief delay (0.4 seconds) in each loop execution. Even though it is not necessary to use this method, it is preferable to use it here to slow down the background color's changing speed of the label2 UILabel (our color rotator). Additionally in each loop we create random values for the red, green, and blue. We then set these values to produce a custom color and set it as a background color in the label2 UILabel.

At this point the two tasks that are going to be executed at the same time with the main thread are ready. Let's implement the three (really easy) IBAction methods and then we are ready to go. As I have already mentioned, the three UIButtons will change the view's background color, with the ultimate goal to demonstrate how the main thread can run alongside the other two tasks. Here they are:

01
02
03
04
05
06
07
08
09
10
11
- (IBAction)applyBackgroundColor1 {
    [self.view setBackgroundColor:[UIColor colorWithRed:255.0/255.0 green:204.0/255.0 blue:102.0/255.0 alpha:1.0]];
}
 
- (IBAction)applyBackgroundColor2 {
    [self.view setBackgroundColor:[UIColor colorWithRed:204.0/255.0 green:255.0/255.0 blue:102.0/255.0 alpha:1.0]];
}
 
- (IBAction)applyBackgroundColor3 {
    [self.view setBackgroundColor:[UIColor whiteColor]];
}

That's it! Now you can run the application and see how three different tasks can take place at the same time. Remember that when the execution of NSOperation objects is over, it will automatically leave the queue.

Conclusion

Many of you may have already discovered that the actual code to run a multi-tasking app only requires a few lines of code. It seems that the greatest workload is implementing the required methods that work with each task. Nevertheless, this method is an easy way to develop multi-threading apps in iOS.

Working with the NSOperationQueue Class的更多相关文章

  1. Cocoa深入学习:NSOperationQueue、NSRunLoop和线程安全 (转)

    目前在 iOS 和 OS X 中有两套先进的同步 API 可供我们使用:NSOperation 和 GCD .其中 GCD 是基于 C 的底层的 API ,而 NSOperation 则是 GCD 实 ...

  2. 多线程下NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue的使用

    本篇文章主要介绍下多线程下NSOperation.NSBlockOperation.NSInvocationOperation.NSOperationQueue的使用,列举几个简单的例子. 默认情况下 ...

  3. NSOperationQueue的其他方法

    1.设置最大并发数 什么是并发数 同时执行的任务数 比如,同时开3个线程执行3个任务,并发数就是3   最大并发数的相关方法 - (NSInteger)maxConcurrentOperationCo ...

  4. NSOperationQueue的基本使用

    NSOperationQueue的作用 NSOperation可以调用start方法来执行任务,但默认是同步执行的 如果将NSOperation添加到NSOperationQueue(操作队列)中,系 ...

  5. NSOperationQueue与GCD的使用原则和场景

    首先,我们要明确NSOperationQueue与GCD之间的关系: NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象. 其次,我们要区别两者的不同: GCD仅仅支持FIFO队列 ...

  6. 伟大的GCD和NSOperationQueue

    一. GCD GCD中最重要的两个东西 任务 和 队列 任务就是一段代码(用来缓存,下载,计算等操作) 队列从大的方面分为两个队列:主队列(串行队列)和 自己创建的队列(串行,和并行) 主队列中: 在 ...

  7. 多线程NSInvocationOperation(NSOperationQueue)的基本用法

        #import "ViewController.h" @interface ViewController () @end @implementation ViewContr ...

  8. IOS 多线程02-pthread 、 NSThread 、GCD 、NSOperationQueue、NSRunLoop

    注:本人是翻译过来,并且加上本人的一点见解. 要点: 1.前言 2.pthread 3.NSThread 4.Grand Central Dispatch(GCD) 5.Operation Queue ...

  9. GCD与NSOperationQueue

    1> GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装 2> GCD只支持FIFO(先入先出)的队列,NSOperationQueue可以很方便地调整执 ...

  10. 多线程编程4 - NSOperationQueue

    一.简介 一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的.也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步 ...

随机推荐

  1. Ajax---概念介绍

    Ajax不是某种编程语言,是一种在无需重新加载整个网页的情况下能够更新部分网页的技术. 运用HTML和CSS来实现页面,表达信息: 运用XMLHttpRequest和Web服务器进行数据的异步交换: ...

  2. Nginx与Tomcat集成

    Nginx用来处理静态页面的请求,JSP交给Tomcat处理 安装JDK 安装后,配置好JAVA_HOME和PATH Mac查看JAVA_HOME路径的方法:/usr/libexec/java_hom ...

  3. 【转】VS常用快捷键

    每次在网上搜关于VS有哪些常用快捷键的时候,出来的永远是一串长的不能再长的列表,完全没体现出“常用”二字,每次看完前面几个就看不下去了,相信大家都 有这种感觉.其实我们平时用的真的只有很少的一部分,借 ...

  4. 2016-2017 ACM-ICPC, Egyptian Collegiate Programming Contest (ECPC 16)

    A.The game of Osho(sg函数+二项展开) 题意: 一共有G个子游戏,一个子游戏有Bi, Ni两个数字.两名玩家开始玩游戏,每名玩家从N中减去B的任意幂次的数,直到不能操作判定为输.问 ...

  5. 决策树与随机森林Adaboost算法

    一. 决策树 决策树(Decision Tree)及其变种是另一类将输入空间分成不同的区域,每个区域有独立参数的算法.决策树分类算法是一种基于实例的归纳学习方法,它能从给定的无序的训练样本中,提炼出树 ...

  6. POJ 3090 Visible Lattice Points | 其实是欧拉函数

    题目: 给一个n,n的网格,点可以遮挡视线,问从0,0看能看到多少点 题解: 根据对称性,我们可以把网格按y=x为对称轴划分成两半,求一半的就可以了,可以想到的是应该每种斜率只能看到一个点 因为斜率表 ...

  7. 《R语言实战》读书笔记--第三章 图形初阶(二)

    3.4添加文本.自定义坐标轴和图例 很多作图函数可以设置坐标轴和文本标注.比如标题.副标题.坐标轴标签.坐标轴范围等.需要注意的是并不是所有的绘图函数都有上述的参数,需要进行验证.可以将一些默认的参数 ...

  8. spring in action学习笔记十六:配置数据源的几种方式

    第一种方式:JNDI的方式. 用xml配置的方式的代码如下: 1 <jee:jndi-lookup jndi-name="/jdbc/spittrDS" resource-r ...

  9. Linux/unix inode

    一.inode是什么? 理解inode,要从文件储存说起. 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector).每个扇区储存512字节(相当于0.5KB). 操作系统 ...

  10. VLC for iOS 2.3.0

    http://www.cocoachina.com/bbs/read.php?tid=231898 VLC for iOS 2.3.0       本帖属于CocoaChina会员发表,转帖请写明来源 ...