[关闭]
@qidiandasheng 2016-09-18T19:13:29.000000Z 字数 7273 阅读 2366

多线程之NSOperation

iOS理论


前言

NSOperation是在OC中处理多线程的一种方式,我们可以配合使用NSOperationNSOperationQueue实现多线程编程。实现过程大致如下:

1:先将需要执行的操作封装到一个NSOperation对象中

2:然后将NSOperation对象添加到NSOperationQueue中

3:系统会自动将NSOperation中封装的操作放到一条新线程中执行

在此过程中,我们根本不用考虑线程的生命周期、同步、加锁等问题。

默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:

1:NSInvocationOperation

2:NSBlockOperation

3:自定义子类继承NSOperation,实现内部相应的方法

NSInvocationOperation

这是第一种封装操作的方式。我们现在来写一个最简单的例子:

  1. - (void)operationStart{
  2. //初始化了一个NSInvocationOperation对象
  3. NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(writeName:) object:@"齐滇大圣"];
  4. //开始执行operation里面封装好的操作
  5. [operation start];
  6. NSLog(@"operation end");
  7. }
  8. - (void)writeName:(NSString *)name{
  9. NSLog(@"%@ %@",name,[NSThread currentThread]);
  10. sleep(3);
  11. }
  12. //输出
  13. 2016-09-04 10:14:50.884 NSOperationDemo[58483:8251571] 齐滇大圣 <NSThread: 0x7fb2a0507e20>{number = 1, name = main}
  14. 2016-09-04 10:14:53.887 NSOperationDemo[58483:8251571] operation end

我们看到[operation start];writeName方法开始执行,而这里输出的线程是在主线程。我们看一下输出的时间发现要等writeName操作结束后(3s后)NSLog(@"operation end");才执行。
所以这种直接[operation start];是同步的在当前线程上执行,会阻塞当前线程。一般不太推荐使用。

那如果我们不想同步怎么办,那可以把operation加入到NSOperationQueue中。

  1. - (void)operationStart{
  2. NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(writeName:) object:@"齐滇大圣"];
  3. NSOperationQueue *queue = [NSOperationQueue mainQueue];
  4. [queue addOperation:operation];
  5. NSLog(@"operation end");
  6. }
  7. - (void)writeName:(NSString *)name{
  8. NSLog(@"%@ %@",name,[NSThread currentThread]);
  9. sleep(3);
  10. }
  11. //输出:
  12. 2016-09-04 10:21:58.067 NSOperationDemo[58599:8258279] operation end
  13. 2016-09-04 10:21:58.069 NSOperationDemo[58599:8258279] 齐滇大圣 <NSThread: 0x7fc461601f80>{number = 1, name = main}

我们看到当加入到NSOperationQueue中的时候,操作还是在主线程执行的,但是是异步的,没有阻塞当前线程。两个输出的时间几乎是同时的。

[NSOperationQueue mainQueue]我们能看出这个是获取主线程上的操作队列。我们也可以把[NSOperationQueue mainQueue]换成[NSOperationQueue new](自己创建的操作队列)。

我们发现writeName任务就没在主线程上执行了。相当于你创建的操作队列里面的operation系统会自动给你分配一个线程去处理里面封装的操作。

NSBlockOperation

这种方式相对于上一种其实就是把任务封装成了一个block加入到了operation中。

  1. - (void)operationStart{
  2. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
  3. NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
  4. sleep(3);
  5. }];
  6. // 开始执行任务
  7. [operation start];
  8. NSLog(@"operation end");
  9. }
  10. //输出:
  11. 2016-09-04 10:47:26.129 NSOperationDemo[58899:8277981] 齐滇大圣 <NSThread: 0x7f8969604da0>{number = 1, name = main}
  12. 2016-09-04 10:47:29.132 NSOperationDemo[58899:8277981] operation end

我们看一下两个输出的时间差,发现这种方式其实也是同步的,会阻塞当前线程。

那是不是如上一种方式一样把operation放入NSOperationQueue中就行了呢?我们来看一下:

  1. //加入到mainQueue中
  2. - (void)operationStart{
  3. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
  4. NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
  5. sleep(3);
  6. }];
  7. NSOperationQueue *queue = [NSOperationQueue mainQueue];
  8. [queue addOperation:operation];
  9. NSLog(@"operation end");
  10. }
  11. //输出:
  12. 2016-09-04 10:53:18.406 NSOperationDemo[59091:8284999] operation end
  13. 2016-09-04 10:53:18.408 NSOperationDemo[59091:8284999] 齐滇大圣 <NSThread: 0x7ff3b9405ce0>{number = 1, name = main}
  1. //加入到自己创建的`NSOperationQueue`中
  2. - (void)creatOperationStart{
  3. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
  4. NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
  5. sleep(3);
  6. }];
  7. NSOperationQueue *queue = [NSOperationQueue new];
  8. [queue addOperation:operation];
  9. NSLog(@"operation end");
  10. }
  11. //输出:
  12. 2016-09-04 10:53:21.911 NSOperationDemo[59091:8284999] operation end
  13. 2016-09-04 10:53:21.915 NSOperationDemo[59091:8285185] 齐滇大圣 <NSThread: 0x7ff3b942bf90>{number = 2, name = (null)}

我们发现也是可以像NSInvocationOperation放入mainqueue或自己创建的NSOperationQueue中的。都是异步执行。

其实[operation start];也可以多线程执行的。当operation中加入了多个block操作的时候,其实系统就会自动分配新线程实现多并发。

但是相对于整个主线程来说这个operation还是同步的,也就是说主线程会等所有的operation里的操作都完成后[operation start];后面的才继续执行。

下面我们看例子:

  1. - (void)operationStart{
  2. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
  3. NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
  4. sleep(3);
  5. }];
  6. [operation addExecutionBlock:^{
  7. NSLog(@"A %@",[NSThread currentThread]);
  8. }];
  9. [operation addExecutionBlock:^{
  10. NSLog(@"B %@",[NSThread currentThread]);
  11. sleep(5);
  12. }];
  13. [operation addExecutionBlock:^{
  14. NSLog(@"C %@",[NSThread currentThread]);
  15. }];
  16. // 开始执行任务
  17. [operation start];
  18. NSLog(@"operation end");
  19. }
  20. //输出:
  21. 2016-09-04 11:12:58.061 NSOperationDemo[59382:8299618] B <NSThread: 0x7fb5cb4699d0>{number = 2, name = (null)}
  22. 2016-09-04 11:12:58.061 NSOperationDemo[59382:8299611] 齐滇大圣 <NSThread: 0x7fb5cb52c1e0>{number = 3, name = (null)}
  23. 2016-09-04 11:12:58.061 NSOperationDemo[59382:8299486] A <NSThread: 0x7fb5cb506170>{number = 1, name = main}
  24. 2016-09-04 11:12:58.061 NSOperationDemo[59382:8299604] C <NSThread: 0x7fb5cb75d8c0>{number = 4, name = (null)}
  25. 2016-09-04 11:13:03.125 NSOperationDemo[59382:8299486] operation end

我们看到operation end的输出跟之前的相差了5s,我们在B那里sleep了5s,虽然B不是在主线程上执行的,但是NSLog(@"operation end");也要等B执行完了之后才能执行。

所以说虽然operation加入了多个block任务发生了并发。我们可以理解为这些任务相对于operation来说它就是一整个完整的任务,下面有多个子任务而已。只有当这一整个任务完成了[operation start];后面的才能执行。

自定义的NSOperation

有时候NSInvocationOperationNSBlockOperation不能满足需求,我们可以直接新建子类继承NSOperation,并添加任何需要执行的操作。

我们所熟悉的SDWebImage中的下载操作类SDWebImageDownloaderOperation就是继承自NSOperation的子类。

SDWebImageDownloaderOperation这篇文章有讲解SDWebImageDownloaderOperation如何使用及实现。

自定义的NSOperation有两种一种是非并发的,一种是并发的。

自定义并发的比较容易,只需要实现以下两个方法:
- 自定义初始化方法
- main方法

自定义非并发的就比较麻烦了,SDWebImageDownloaderOperation就是自定义非并发的,我们看一下下面这张表:

方法 描述
start (必选)所有的并发Operation必需重写这个方法并且要实现这个方法的内容来代替原来的操和。手动执行一个操作,你可以调用start方法。因此,这个方法的实现是这个操作的始点,也是其他线程或者运行这你这个任务的起点。注意一下,在这里永远不要调用[super start]。
main (可选)这个方法就是你的实现的操作
isExecuting 和 isFinish (必选)并发队列负责维持当前操作的环境和告诉外部调用者当前的运行状态。因此,一个并发队列必需维持保持一些状态信息以至于知道什么时候执行任务,什么时候完成任务。它必须通过这些方法告诉外部当前的状态。这种而且这些方法必须是线程安全,当状态发生改变的时候,你必须使用KVO通知监听这些状态的对象。
isConcurrent (必选)定义一个并发操作,重写这个方法并且返回YES

NSOperation API

[operation cancel]取消操作

operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作。

当向一个Operation对象发送cancel消息后,并不保证这个Operation对象一定能立刻取消,这取决于你的main中对cancel的处理。如果你在main方法中没有对cancel进行任何处理的话,发送cancel消息是没有任何效果的。为了让Operation响应cancel消息,那么你就要在main方法中一些适当的地方手动的判断isCancelled属性,如果返回YES的话,应释放相关资源并立刻停止继续执行。

上诉的main可以是start

比如SDWebImageDownloaderOperation类中的- (void)start:

  1. - (void)start {
  2. @synchronized (self) {
  3. if (self.isCancelled) {
  4. self.finished = YES;
  5. [self reset];
  6. return;
  7. }
  8. //下面是一些实际的操作
  9. }
  10. }

completionBlock操作完成后的回调

如果想在一个NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情

  1. operation.completionBlock = ^() {
  2. NSLog(@"执行完毕");
  3. };

当operation封装的操作执行完毕后,就会回调Block里面的内容


addDependency添加操作依赖

表示某个operationB依赖于operationA,只有当operationA操作完成的时候,operationB才开始执行。
[operationB addDependency:operationA];

所以我们用这个就可以实现多个异步操作完成后得到完成的通知,如下:

  1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  2. NSBlockOperation *p1 = [NSBlockOperation blockOperationWithBlock:^{
  3. NSLog(@"A");
  4. }];
  5. NSBlockOperation *p2 = [NSBlockOperation blockOperationWithBlock:^{
  6. NSLog(@"B");
  7. }];
  8. NSBlockOperation *p3 = [NSBlockOperation blockOperationWithBlock:^{
  9. NSLog(@"C");
  10. }];
  11. NSBlockOperation *pfinish = [NSBlockOperation blockOperationWithBlock:^{
  12. NSLog(@"Finish");
  13. }];
  14. [pfinish addDependency:p1];
  15. [pfinish addDependency:p2];
  16. [pfinish addDependency:p3];
  17. //!!!所以pfinish一定是最后输出,p1、p2、p3异步顺序不一定。
  18. // waitUntilFinished是否阻塞当前线程
  19. [queue addOperations:@[p1,p2,p3,pfinish] waitUntilFinished:YES];
  20. // 如果是NO,那么这行打印就是随机的。YES表示阻塞当前线程那么就是要等A,B,C,Finish都打印完之后才打印HAHA
  21. NSLog(@"HAHA");

NSOperationQueue API

取消队列的所有操作

  1. - (void)cancelAllOperations;

暂停和恢复队列

  1. // YES代表暂停队列,NO代表恢复队列
  2. - (void)setSuspended:(BOOL)b;
  3. //当前状态
  4. - (BOOL)isSuspended;

添加操作

添加操作有对应的三个方法:

  1. - (void)addOperation:(NSOperation *)op;
  2. //waitUntilFinished表示是否阻塞当前线程,YES表示阻塞
  3. - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
  4. - (void)addOperationWithBlock:(void (^)(void))block ;

NSOperationDemo

对于NSOperation使用的Demo:NSOperationDemo

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注