NSOperation表示了一个独立的计算单元。作为一个抽象类,它给了它的子类一个十分有用而且线程安全的方式来建立状态、优先级、依赖性和取消等的模型;不用我们考虑线程的生命周期、同步、加锁等问题。
NSOperationQueue
NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。
主队列运行在主线程之上,而自定义队列在后台执行。
1 2 3 4 5 6
| NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ }]; [queue addOperation:operation];
|
我们可以通过设置 maxConcurrentOperationCount
属性来控制并发任务的数量,当设置为 1
时, 那么它就是一个串行队列。主对列默认是串行队列,这一点和 dispatch_queue_t
是相似的。
NSInvocationOperation
1 2
| NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"mj"] autorelease]; [operation start];
|
注意:默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。只有将operation放到一个NSOperationQueue中,才会异步执行操作。
NSBlockOperation
同步执行一个操作
1 2 3 4 5
| NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ NSLog(@"执行了一个新的操作"); }]; [operation start];
|
这里还是在当前线程同步执行操作,并没有异步执行
并发执行多个操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^() { NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^() { NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^() { NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]); }]; [operation start];
|
在第18行调用start方法后,就会并发地执行这4个操作,也就是会在不同线程中执行
1 2 3 4
| 2013-02-02 21:38:46.102 thread[4602:c07] 又执行了1个新的操作,线程:<NSThread: 0x7121d50>{name = (null), num = 1} 2013-02-02 21:38:46.102 thread[4602:3f03] 又执行了1个新的操作,线程:<NSThread: 0x742e1d0>{name = (null), num = 5} 2013-02-02 21:38:46.102 thread[4602:1b03] 执行第1次操作,线程:<NSThread: 0x742de50>{name = (null), num = 3} 2013-02-02 21:38:46.102 thread[4602:1303] 又执行了1个新的操作,线程:<NSThread: 0x7157bf0>{name = (null), num = 4}
|
可以看出,每个操作所在线程的num值都不一样,说明是不同线程
自定义NSOperation
直接新建子类继承NSOperation,通过重写 main
或者 start
方法来定义自己的operations
使用 main
方法非常简单,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。这种方式使用起来非常简单,但是灵活性相对重写 start 来说要少一些, 因为main方法执行完就认为operation结束了,所以一般可以用来执行同步任务。
1 2 3 4 5 6
| @implementation YourOperation - (void)main { } @end
|
如果你希望拥有更多的控制权,或者想在一个操作中可以执行异步任务,那么就重写 start
方法, 但是注意:这种情况下,你必须手动管理操作的状态, 只有当发送 isFinished 的 KVO 消息时,才认为是 operation 结束
1 2 3 4 5 6 7 8 9 10 11 12
| @implementation YourOperation - (void)start { self.isExecuting = YES; } - (void)finish { self.isExecuting = NO; self.isFinished = YES; } @end
|
当实现了start方法时,默认会执行start方法,而不执行main方法
状态管理
如果你不使用 状态属性 默认的 setter 来进行设置的话,为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO
的方式进行实现。
需要手动管理的状态有:
1 2 3
| isExecuting 代表任务正在执行中 isFinished 代表任务已经执行完成 isCancelled 代表任务已经取消执行
|
手动的发送 KVO 消息, 通知状态更改如下 :
1 2 3
| [self willChangeValueForKey:@"isCancelled"]; _isCancelled = YES; [self didChangeValueForKey:@"isCancelled"];
|
为了能使用操作队列所提供的取消功能,你需要在长时间操作中时不时地检查 isCancelled
属性, 比如在一个长的循环中:
1 2 3 4 5 6 7 8 9
| @implementation MyOperation - (void)main { while (notDone && !self.isCancelled) { } } @end
|
另外执行了一段 比较耗时的操作 之后,都需要判断操作有没有被取消;如果被取消了,那就没有必要往下执行了。
优先级
通过以下的顺序设置 queuePriority
属性可以加快或者推迟操作的执行:
1 2 3 4 5
| NSOperationQueuePriorityVeryHigh NSOperationQueuePriorityHigh NSOperationQueuePriorityNormal NSOperationQueuePriorityLow NSOperationQueuePriorityVeryLow
|
此外,有些操作还可以指定 threadPriority
的值,它的取值范围可以从0.0到1.0,1.0代表最高的优先级。
鉴于queuePriority属性决定了操作执行的顺序,threadPriority则指定了当操作开始执行以后的CPU计算能力的分配
依赖性
根据应用的复杂度不同,将大任务再分成一系列子任务一般都是很有意义的,而你能通过NSOperation的依赖性实现。
1 2 3 4 5
| [resizingOperation addDependency:networkingOperation]; [operationQueue addOperation:networkingOperation]; [operationQueue addOperation:resizingOperation];
|
除非一个操作的依赖的 isFinished
返回YES,不然这个操作不会开始。
此外,确保不要意外地创建依赖循环,像A依赖B,B又依赖A,这也会导致杯具的死锁。
completionBlock
每当一个NSOperation执行完毕,它就会调用它的 completionBlock
属性一次。