文章目录
  1. 1. NSOperationQueue
  2. 2. NSInvocationOperation
  3. 3. NSBlockOperation
    1. 3.1. 同步执行一个操作
    2. 3.2. 并发执行多个操作
  4. 4. 自定义NSOperation
  5. 5. 状态管理
  6. 6. 优先级
  7. 7. 依赖性
  8. 8. completionBlock

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];
//TODO: 别忘了把两个 operation 都添加到 queue 中
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];

除非一个操作的依赖的 isFinished 返回YES,不然这个操作不会开始。

此外,确保不要意外地创建依赖循环,像A依赖B,B又依赖A,这也会导致杯具的死锁。

completionBlock

每当一个NSOperation执行完毕,它就会调用它的 completionBlock 属性一次。