多线程(三):NSOperation

iOS多线程之NSOperation

Posted by Ted on May 24, 2016

1、NSoperation是基于GCD封装的

dispatch_async(_Queue, ^{    //请求数据  
NSData *data = [NSData dataWithContentURL:[NSURL URLWithString:@"http://domain.com/a.png"]];    
dispatch_async(dispatch_get_main_queue(), ^{         [self refreshViews:data];    });});

有个致命的问题:这个任务是无法取消的 dataWithContentURL:是同步的拉取数据,它会一直阻塞线程直到完成请求,如果是遇到了超时的情况,它在这个时间内会一直占有这个线程;在这个期间并发队列就需要为其他任务新建线程,这样可能导致性能下降等问题。

2、NSOperationQueue相对于GCD来说有以下优点:

  • 提供了在 GCD 中不那么容易复制的有用特性。
  • 可以很方便的取消一个NSOperation的执行
  • 可以更容易的添加任务的依赖关系
  • 提供了任务的状态:isExecuteing, isFinished.

NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。

我们可以通过设置 maxConcurrentOperationCount 属性来控制并发任务的数量,当设置为 1 时, 那么它就是一个串行队列。主对列默认是串行队列,这一点和 dispatch_queue_t 是相似的。

Operation 对象默认按同步方式执行,也就是在调用 start 方法的那 个线程中直接执行。

3、用法

NSOperation实现

一个是直接使用NSInvocationOperation、NSBlockOperation两个子类,一个是自己实现NSOperation的子类

NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:selfselector:@selector(invocationOperationAction)object:nil];
NSBlockOperation*blockOperation = [NSBlockOperationblockOperationWithBlock:^{

}];

执行任务有两种方法:

  • start :方法时与主线程同步,有阻塞主线程的情况,
  • 提交到NSOperationQueue中:与主线程是异步,不会阻塞到主线程。NSOperationQueue可以通过maxConcurrentOperationCount设置线程最大并行数量,为1的时候相当于串行,大于1时为并发。

取消任务

//取消队列中的所有operation
[queue cancelAllOperations];

//取消单个
[blockOperation cancel];

暂停和恢复

[queue setSuspended:YES];

挂起一个 queue 不会导致正在执行的 Operation 在任务中途暂停,只是简单地阻止调度新 Operation 执行,也就是只是暂停等待中的任务。你 可以在响应用户请求时,挂起一个 queue,来暂停等待中的任务。稍后 根据用户的请求,可以再次调用 setSuspended: 方法继续 Queue 中操作 的执行。

任务依赖:

[operationB addDependency:operationA]; // 操作B依赖于操作A

自定义Operation

A:使用 main 方法非常简单,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。这种方式使用起来非常简单,但是灵活性相对重写 start 来说要少一些, 因为main方法执行完就认为operation结束了,所以一般可以用来执行同步任务。

B:如果你希望拥有更多的控制权,或者想在一个操作中可以执行异步任务,那么就重写 start 方法, 但是注意:这种情况下,你必须手动管理操作的状态, 只有当发送 isFinished 的 KVO 消息时,才认为是 operation 结束

当实现了start方法时,默认会执行start方法,而不执行main方法

为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现。如果你不使用它们默认的 setter 来进行设置的话,你就需要在合适的时候发送合适的 KVO 消息。

需要手动管理的状态有:

  • isExecuting 代表任务正在执行中
  • isFinished 代表任务已经执行完成
  • isCancelled 代表任务已经取消执行