多线程编程NSOperationQueue

Posted 泛彼无垠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程编程NSOperationQueue相关的知识,希望对你有一定的参考价值。

本文我们来介绍一下多线程编程工具中的NSOperationQueue。


1. NSOperationQueue简介

配合使用NSOperation和NSOperationQueue也可以实现多线程编程。使用NSOperationQueue方式进行多线程编程,不能够像NSThread一样直接创建线程,也不需要管理,但是可以间接的干预线程,这也是该方式的优点。NSOperationQueue同时引入了Queue(队列)的概念,了解NSOperationQueue使用,首先要了解NSOperation和NSOperationQueue的关系。


2. NSOperation的使用

2.1 NSOperation与NSOperationQueue的关系

NSOperation类是一个抽象类,用来封装单任务的代码和数据。可以参考其英文解释:

   The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task.你使用的NSOperation类是一个抽象类来封装与单个任务相关的代码和数据

所以,我们不能直接使用该类,而是使用系统定义的子类来完成实际的任务。ios 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation,当然也可以自定义 NSOperation。

需要注意的是,使用NSOperation的子类对象只能执行一次任务,而且不能再次执行他,可以将他添加到一个操作队列中执行操作,这个操作队列我们可以使用NSOperationQueue来实现。

2.2 创建NSOperation的创建方式

   NSOperation是个抽象类,并不具备封装操作的能力,必须使?它的子类
   使用NSOperation?类的方式有3种:
 (1)NSInvocationOperation
 (2)NSBlockOperation
 (3)自定义子类继承NSOperation,实现内部相应的?法。

NSBlockOperation可以通过addExecutionBlock添加新的任务,只要NSBlockOperation封装的操作数大于1,就会异步执行操作。

NSOperation也是设计用来扩展的,只需继承重写 NSOperation的一个方法main。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。

   注意,需要强调的是,NSOperation默认是不执行的,需要调用start方法。同时,操作对象默认在主线程中执行,只有添加到队列中才会开启新的线程。即默认情况下,如果操作没有放到队列中queue中,都是同步执行。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

3. NSOperationQueue的使用

NSOperationQueue的作?:NSOperation可以调?start?法来执?任务,但默认是同步执行的。如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。

NSOperation和NSOperationQueue实现多线程的具体步骤:
(1)先将需要执行的操作封装到一个NSOperation对象中
(2)然后将NSOperation对象添加到NSOperationQueue中
(3)系统会?动将NSOperationQueue中的NSOperation取来
(4)将取出的NSOperation封装的操作放到?条新线程中执?

注意:一个NSOperationQueue对象并非一个线程,而是线程管理器,可以帮我们自动创建新的线程。取决于队列的最大并行数。NSOperation对象添加到队列中,默认就成为了异步执行(非当前线程)。

- 创建一个操作队列

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

- 添加一个operation

      [queue addOperation:operation];

- 添加一组operation

      [queue addOperations:operations waitUntilFinished:NO];

- 添加一个block形式/隐式的operation

     [queue addOperationWithBlock:^() {
         NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]);
     }];

NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。

   注意:NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态,比如是否正在运行、等待运行、已经完成等。

3. NSOperation的运行顺序

在执行NSOperationqueue中的任务时,系统自动将NSOperationqueue中的NSOperation对象取出,将其封装的操作放到一条新的线程中执行。如果四个任务,开启了四条线程。通过查看任务执行的时间可以看出,这些任务是并行执行的。

队列的取出是有顺序的,与打印结果并不矛盾。这就好比,选手A,B,C虽然起跑的顺序是先A,后B,然后C,但是到达终点的顺序却不一定是A,B在前,C在后。
对于添加到queue中的operations,它们的执行顺序取决于2点:

  • 首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
  • 然后再根据所有NSOperation的相对优先级来确定。

3.1 NSOperation添加依赖关系

依赖关系会影响到NSOperation对象在queue中的执行顺序,当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象:

[operation2 addDependency:operation1];

只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。

[operation2 removeDependency:operation1];

添加依赖成功的前提,必须将Operation添加到Queue中,但是要在将Operation添加到Queue中之前添加依赖。依赖关系不局限于相同queue中的NSOperation对象。NSOperation对象会管理自己的依赖,,因此完全可以在不同的queue之间的NSOperation对象创建依赖关系。

注意:唯一的限制是不能创建环形依赖,比如A依赖B,B依赖A,这是错误的

3.2 修改Operations的优先级

优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能

优先级的取值

  • NSOperationQueuePriorityVeryLow = -8L,

  • NSOperationQueuePriorityLow = -4L,

  • NSOperationQueuePriorityNormal = 0,
  • NSOperationQueuePriorityHigh = 4,

  • NSOperationQueuePriorityVeryHigh = 8

    说明:优先级高的任务,调用的几率会更大。
    注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations 确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。

4.4 操作的监听

如果需要设置A任务的执行晚于B任务,可以直接跟在任务后面编写需要完成的操作,如这里在下载图片后,紧跟着下载第二张图片。但是这种写法有的时候把两个不相关的操作写到了一个代码块中,代码的可阅读性不强。

通过下面两个方法,可以监听一个操作的执行完毕,在添加其他任务。

- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

示例代码:

//创建对象,封装操作

NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
    for (int i=0; i<10; i++) {
        NSLog(@"-operation-下载图片-%@",[NSThread currentThread]);
    }
}];
//监听操作的执行完毕
operation.completionBlock=^{
   //.....下载图片后继续进行的操作
    NSLog(@"--接着下载第二张图片--");
};
//创建队列
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
//把任务添加到队列中(自动执行,自动开线程)
[queue addOperation:operation];

4. NSOperationQueue的相关操作

4.1 并发数

并发数:同时执?行的任务数.比如,同时开3个线程执行3个任务,并发数就是3
最大并发数:同一时间最多只能执行的任务的个数。

最?大并发数的相关?方法

- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
   说明:如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的,可能内存多久开多一点,内存少就开少一点。
   注意:num的值并不代表线程的个数,仅仅代表线程的ID。
   提示:最大并发数不要乱写(5以内),不要开太多,一般以2~3为宜,因为虽然任务是在子线程进行处理的,但是cpu处理这些过多的子线程可能会影响UI,让UI变卡。

虽然NSOperationQueue类设计用于并发执行Operations,,你也可以强制单个queue一次只能执行一个Operation。setMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量。设为1就表示queue每次只能执行一个操作, 设置最大并行数为1时,所有op都会串行。但是并不代表只有一个线程,operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于GCD中的串行dispatch queue

4.2 队列的取消,暂停和恢复

  • 取消队列的所有操作

    一旦添加到operation queue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消。你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queue的cancelAllOperations方法取消当前queue中的所有操作。

    • (void)cancelAllOperations;
      提示:也可以调用NSOperation的- (void)cancel?法取消单个操作
  • 暂停和恢复队列

    如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended:方法继续queue中operation的执行。
    
    • (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
    • (BOOL)isSuspended; //当前状态
      需要强调的是,暂停和恢复的适用场合:在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。

4.3 Operations的同步执行

操作放置到队列中,默认异步执行。为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情。如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。

除了等待单个Operation完成,你也可以同时等待一个queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。注意:在等待一个queue时,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间。

5. 小结:

使用NSOperationQueue来实现多线程编程是iOS常用的三中多线程编程工具中理解起来最简单的,而且我们发现操作方法也并不是太多。但是NSOperationQueue实现的功能并不是太多,这可能也是它并不流行的一个原因。

以上是关于多线程编程NSOperationQueue的主要内容,如果未能解决你的问题,请参考以下文章

多线程 NSOperation

Cocoa Touch: 多线程GCD, NSObject, NSThread, NSOperationQueue, @synchronized

多线程-NSOperation

iOS多线程---NSOperation介绍和使用

iOS开发多线程篇 09 —NSOperation简单介绍

多线程编程2-NSOperation