Working with the NSOperationQueue Class
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:
- UINavigationBar
- Frame (x, y, W, H): 0, 0, 320, 44
- Tintcolor: Black color
- Title: "Simple Multi-Threading Demo"
- UILabel
- Frame (x, y, W, H): 20, 59, 280, 21
- Text: "Counter at Thread #1"
- UILabel
- Frame (x, y, W, H): 20, 88, 280, 50
- Background color: Light gray color
- Text color: Dark gray color
- Text: -
- UILabel
- Frame (x, y, W, H): 20, 154, 280, 21
- Text: "Random Color Rotator at Thread #2"
- UILabel
- Frame (x, y, W, H): 20, 183, 100, 80
- Background color: Light gray color
- Text: -
- UILabel
- Frame (x, y, W, H): 128, 183, 150, 80
- Text: -
- UILabel
- Frame (x, y, W, H): 20, 374, 280, 21
- Text: "Background Color at Main Thread"
- UIButton
- Frame (x, y, W, H): 20, 403, 73, 37
- Title: "Color #1"
- UIButton
- Frame (x, y, W, H): 124, 403, 73, 37
- Title: "Color #2"
- 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 *label 1 ; @property ( retain , nonatomic ) IBOutlet UILabel *label 2 ; @property ( retain , nonatomic ) IBOutlet UILabel *label 3 ; |
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 )applyBackgroundColor 1 ; - ( IBAction )applyBackgroundColor 2 ; - ( IBAction )applyBackgroundColor 3 ; |
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 NSOperationQueue
object (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 *label 1 ; @property ( retain , nonatomic ) IBOutlet UILabel *label 2 ; @property ( retain , nonatomic ) IBOutlet UILabel *label 3 ; - ( IBAction )applyBackgroundColor 1 ; - ( IBAction )applyBackgroundColor 2 ; - ( IBAction )applyBackgroundColor 3 ; -( 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< 1 0 0 0 0 0 0 0 ; i++) { if (i % 1 0 0 == 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. [label 1 performSelectorOnMainThread : @selector (setText:) withObject :[ NSString stringWithFormat : @"%d" , i ] waitUntilDone : YES ]; } } // When the loop gets finished then just display a message. [label 1 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 performSelectorOnMainThread
method 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< 5 0 0 ; i++) { // Create three float random numbers with values from 0.0 to 1.0. float redColorValue = (arc 4 random() % 1 0 0 ) * 1 .0 / 1 0 0 ; float greenColorValue = (arc 4 random() % 1 0 0 ) * 1 .0 / 1 0 0 ; float blueColorValue = (arc 4 random() % 1 0 0 ) * 1 .0 / 1 0 0 ; // 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. [label 2 performSelectorOnMainThread : @selector (setBackgroundColor:) withObject :customColor waitUntilDone : YES ]; // Set the r, g, b and iteration number values on label3. [label 3 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. [label 3 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 )applyBackgroundColor 1 { [ self .view setBackgroundColor :[ UIColor colorWithRed : 2 5 5 .0 / 2 5 5 .0 green : 2 0 4 .0 / 2 5 5 .0 blue : 1 0 2 .0 / 2 5 5 .0 alpha : 1 .0 ]]; } - ( IBAction )applyBackgroundColor 2 { [ self .view setBackgroundColor :[ UIColor colorWithRed : 2 0 4 .0 / 2 5 5 .0 green : 2 5 5 .0 / 2 5 5 .0 blue : 1 0 2 .0 / 2 5 5 .0 alpha : 1 .0 ]]; } - ( IBAction )applyBackgroundColor 3 { [ 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的更多相关文章
- Cocoa深入学习:NSOperationQueue、NSRunLoop和线程安全 (转)
目前在 iOS 和 OS X 中有两套先进的同步 API 可供我们使用:NSOperation 和 GCD .其中 GCD 是基于 C 的底层的 API ,而 NSOperation 则是 GCD 实 ...
- 多线程下NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue的使用
本篇文章主要介绍下多线程下NSOperation.NSBlockOperation.NSInvocationOperation.NSOperationQueue的使用,列举几个简单的例子. 默认情况下 ...
- NSOperationQueue的其他方法
1.设置最大并发数 什么是并发数 同时执行的任务数 比如,同时开3个线程执行3个任务,并发数就是3 最大并发数的相关方法 - (NSInteger)maxConcurrentOperationCo ...
- NSOperationQueue的基本使用
NSOperationQueue的作用 NSOperation可以调用start方法来执行任务,但默认是同步执行的 如果将NSOperation添加到NSOperationQueue(操作队列)中,系 ...
- NSOperationQueue与GCD的使用原则和场景
首先,我们要明确NSOperationQueue与GCD之间的关系: NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象. 其次,我们要区别两者的不同: GCD仅仅支持FIFO队列 ...
- 伟大的GCD和NSOperationQueue
一. GCD GCD中最重要的两个东西 任务 和 队列 任务就是一段代码(用来缓存,下载,计算等操作) 队列从大的方面分为两个队列:主队列(串行队列)和 自己创建的队列(串行,和并行) 主队列中: 在 ...
- 多线程NSInvocationOperation(NSOperationQueue)的基本用法
#import "ViewController.h" @interface ViewController () @end @implementation ViewContr ...
- IOS 多线程02-pthread 、 NSThread 、GCD 、NSOperationQueue、NSRunLoop
注:本人是翻译过来,并且加上本人的一点见解. 要点: 1.前言 2.pthread 3.NSThread 4.Grand Central Dispatch(GCD) 5.Operation Queue ...
- GCD与NSOperationQueue
1> GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装 2> GCD只支持FIFO(先入先出)的队列,NSOperationQueue可以很方便地调整执 ...
- 多线程编程4 - NSOperationQueue
一.简介 一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的.也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步 ...
随机推荐
- webpack + less
使用less需要安装 'style-loader','css-loader','less-loader' 三个loader. 安装之后在webpack.config.js配置 const path = ...
- 使用Bootstrap框架的HTML5页面模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【Linux】- 获取root权限命令
1:Redhat系统或者Fedora或者CentOs的Linux发行版,那么在Linux终端输入命令回车: su - root 这样就可以切换到root权限了 2:Ubuntu系统,在Linux终端输 ...
- EXTJS4.0 grid 可编辑模式 配置
首先配置这个参数 plugins:[//插件 Ext.create("Ext.grid.plugin.CellEditing",{ clicksToEdit:1//单元格 点一下就 ...
- vue动态(type可变)input绑定
遇到如下错误: v-model does not support dynamic input types 解决方法: vue 2.5.0以上,支持动态绑定 <input :type=" ...
- BFC,IFC,GFC,FFC
FC的全称是:Formatting Contexts,是W3C CSS2.1规范中的一个概念.它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用. ...
- c语言数组传递
转自:http://blog.csdn.net/xgmiao/article/details/9570825 点击打开链接 数组作为函数实参: C语言中数组作为函数实参时,编译器总是将其解析为指向数组 ...
- nginx支持pathinfo
server { root /webserver/www/api; listen ; server_name api.dnxia.com; location / { if (!-e $request_ ...
- UVa10891 Game of Sum
给定n个数字,A和B可以从这串数字的两端任意选数字,一次只能从一端选取.两人都采用最优策略,A先手,问A和B各自得到数字的和的差值最大为多少? 区间DP F[i][j]表示区间i~j内A能得到的最大数 ...
- RelativeSource
当一个Binding有明确的数据来源时可以通过为Source或ElementName赋值的办法让Binding与之关联,有的时候由于不能确定Source的对象叫什么名字,但知道它与作为Binding目 ...