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. rsync同步数据

    1. rsync 命令格式rsync [OPTION]... SRC DESTrsync [OPTION]... SRC [USER@]HOST:DESTrsync [OPTION]... [USER ...

  2. 【bzoj1070】[SCOI2007]修车 最小费用流

    原文地址:http://www.cnblogs.com/GXZlegend/p/6798411.html 题目描述 同一时刻有N位车主带着他们的爱车来到了汽车维修中心.维修中心共有M位技术人员,不同的 ...

  3. A公司 推荐算法大赛 总结

    一.介绍 ♦通过用户前四个月(04.15~08.15)的用户行为预测用户第五个月(08.15~09.15)将会购买的品牌.用户共有四种行为(type)分别是:点击(0).购买(1).购物车(2).收藏 ...

  4. VS debug 简记

    近两日使用VS2013 Professional版本调试一个c源文件,过程中发现有几个bug,不知是IDE的问题还是我设置有问题,记在这里 1.下面的程序段A和B,区别只是for是否加花括号(标准C规 ...

  5. Robocopy用法

    ----------------[参数]-------------------robocopy  /?------------------------------------------------- ...

  6. College student reflects on getting started in open source(一)

    I just completed the first semester of my second year in college, and I'm reflecting on what I learn ...

  7. .net 过滤 sql防注入类,省地以后每次都要重新弄!

    /// <summary>    /// 过滤不安全的字符串    /// </summary>    /// <param name="Str"&g ...

  8. symfony3常用记忆

    1.控制器里获取当前用户信息 $user = $this->getUser(); 2.判断当前用户是否登录 // yay! Use this to see if the user is logg ...

  9. DataTable 去重合并

    //合并 dt.Merge(dt2); //去重 dt = dt.AsDataView().ToTable(true);

  10. dict和set背后的实现原理

    # 先说结论 ''' dict的性能远大于list 在list中,随着数据的增大,时间也会增大 在dict中,随着数据的增大,时间没有变化 ''' # 目的:我们研究为什么dict的性能远大于list ...