还在用GCD?来看看NSOperation吧

在iOS开发中,谈到多线程,大家第一时间想到的一定是GCD。GCD固然是一套强大的多线程解决方案,能够解决绝大多数的多线程问题,但是他易于上手难于精通且到处是坑的特点也注定了想熟练使用它有一定的难度。而且很多人嘴上天天挂着GCD,实际上对它的实际应用也不甚了解。
再者说,在现在的主流开发模式下,能用到多线程的绝大多数就是网络数据请求和网络图片加载,这两点上AFNetwork+SDWebImage已经能满足几乎所有的需求。而剩下的一小部分,简单好用的NSOperation无疑是比GCD更有优势的。
因此,如果你还是坚持『GCD大法好』,那看到这里就不必再看了。如果你想试一试更简单的方法,那就随我来吧。


什么是NSOperation?

和GCD一样,NSOperation也是苹果提供给我们的一套多线程解决方案。实际上它也是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。
NSOperation是一个抽象基类,基本没有什么实际使用价值。我们使用最多的是系统封装好的NSInvocationOperationNSBlockOperation
不过NSOperation一些通用的方法你要知道

1 NSOperation * operation = [[NSOperation alloc]init];
2 //开始执行
3 [operation start];
4 //取消执行
5 [operation cancel];
6 //执行结束后调用的Block
7 [operation setCompletionBlock:^{
8     NSLog(@"执行结束");
9 }];
使用NSInvocationOperation

NSInvocationOperation的使用方式和给Button添加事件比较相似,需要一个对象和一个Selector。使用方法非常简单。
我们先来写一个方法

1 - (void)testNSOperation
2 {
3 NSLog(@"我在第%@个线程",[NSThread currentThread]);
4 }

然后调用它

1 //创建
2 NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];
3 //执行
4 [invo start];

得到这样的执行结果

执行结果

我们可以看到NSInvocationOperation其实是同步执行的,因此单独使用的话,这个东西也没有什么卵用,它需要配合我们后面介绍的NSOperationQueue去使用才能实现多线程调用,所以这里我们只需要记住有这么一个东西就行了。

使用NSBlockOperation
  • 终于到了我们今天的第一个重点
    NSBlockOperation也是NSOperation的子类,支持并发的实行一个或多个block,使用起来简单又方便
    执行以下代码

     1 NSBlockOperation * blockOperation = [[NSBlockOperation
     2 blockOperationWithBlock:^{
     3    NSLog(@"1在第%@个线程",[NSThread currentThread]);
     4 }];
     5 [blockOperation addExecutionBlock:^{
     6   NSLog(@"2在第%@个线程",[NSThread currentThread]);
     7 }];
     8 [blockOperation addExecutionBlock:^{
     9   NSLog(@"3在第%@个线程",[NSThread currentThread]);
    10 }];
    11 [blockOperation addExecutionBlock:^{
    12   NSLog(@"4在第%@个线程",[NSThread currentThread]);
    13 }];
    14 [blockOperation addExecutionBlock:^{
    15   NSLog(@"5在第%@个线程",[NSThread currentThread]);
    16 }];
    17 [blockOperation addExecutionBlock:^{
    18   NSLog(@"6在第%@个线程",[NSThread currentThread]);
    19 }];

    这里我们多执行两次并比较结果

第一次执行的结果

第二次执行的结果

第三次执行的结果

  • 通过三次不同结果的比较,我们可以看到,NSBlockOperation确实实现了多线程。但是我们可以看到,它并非是将所有的block都放到放到了子线程中。通过上面的打印记录我们可以发现,它会优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,但最大并发数为4(包括主线程在内)。如果block数量大于了4,那么剩下的Block就会等待某个线程空闲下来之后被分配到该线程,且依然是优先分配到主线程。
  • 另外,同一个block中的代码是同步执行的

为了证明以上猜想,我们为它增加更多block,并给每条block添加两行代码。

 1  NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
 2     NSLog(@"1在第%@个线程",[NSThread currentThread]);
 3     NSLog(@"1haha");
 4 }];
 5 [blockOperation addExecutionBlock:^{
 6     NSLog(@"2在第%@个线程",[NSThread currentThread]);
 7     NSLog(@"2haha");
 8 }];
 9 [blockOperation addExecutionBlock:^{
10     NSLog(@"3在第%@个线程",[NSThread currentThread]);
11     NSLog(@"3haha");
12 }];
13 [blockOperation addExecutionBlock:^{
14     NSLog(@"4在第%@个线程",[NSThread currentThread]);
15     NSLog(@"4haha");
16 }];
17 [blockOperation addExecutionBlock:^{
18     NSLog(@"5在第%@个线程",[NSThread currentThread]);
19     NSLog(@"5haha");
20 }];
21 [blockOperation addExecutionBlock:^{
22     NSLog(@"6在第%@个线程",[NSThread currentThread]);
23     NSLog(@"6haha");
24 }];
25 [blockOperation addExecutionBlock:^{
26     NSLog(@"7在第%@个线程",[NSThread currentThread]);
27     NSLog(@"7haha");
28 }];
29 [blockOperation addExecutionBlock:^{
30     NSLog(@"8在第%@个线程",[NSThread currentThread]);
31     NSLog(@"8haha");
32 }];
33 [blockOperation addExecutionBlock:^{
34     NSLog(@"9在第%@个线程",[NSThread currentThread]);
35     NSLog(@"9haha");
36 }];
37 [blockOperation addExecutionBlock:^{
38     NSLog(@"10在第%@个线程",[NSThread currentThread]);
39     NSLog(@"10haha");
40 }];
41
42 [blockOperation start];

然后我们看一下执行结果

执行结果

]

  • 我们可以看到,最大并发数为4,使用同一个线程的block一定是等待前一个block的代码全部执行结束后才执行,且同步执行。

关于最大并发数
在刚才的结果中我们看到最大并发数为4,但这个值并不是一个固定值。4是我在模拟器上运行的结果,而如果我使用真机来跑的话,最大并发数始终为2。因此,具体的最大并发数和运行环境也是有关系的。我们不必纠结于这个数字

所以NSBlockOperation也不是一个理想的多线程解决方案,尽管我们可以在第一个block中创建UI,在其他Block做数据处理等操作,但还是感觉哪里不舒服。
别着急,我们继续往下看

自定义NSOperation

是的,你没看错,NSOperation是可以自定义的。如果NSInvocationOperationNSBlockOperation无法满足你的需求,你可以选择自定义一个NSOperation。
经过上面的分析,我们发现,系统提供的两种NSOperation是一定满足不了我们的需求的。
那我们是不是需要自定义一个NSOperation呢?
答案是,不需要。
自定义NSOperation并不难,但是依然要写不少代码,这违背了我们简单实现多线程的初衷。况且,接下来我会介绍我们今天真正的主角--NSOperationQueue。所以,我打算直接跳过这一个环节。
NSOPerationQueue

简单使用

终于轮到我们今天的主角了。
顾名思义,NSOperationQueue就是执行NSOperation的队列,我们可以将一个或多个NSOperation对象放到队列中去执行。
比如我们上面介绍过的NSInvocationOperation,我们来将它放到队列中来

1 //依然调用上面的那个方法
2 NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];
3
4 NSOperationQueue * queue = [[NSOperationQueue alloc]init];
5 [queue addOperation:invo];

看一下执行结果

执行结果

现在它已经被放到子线程中执行了

我们把刚才写的NSBlockOperation也加到这个Queue中来

 ...原来的代码
 1 //[blockOperation start]; 2 [queue addOperation:blockOperation]; 

然后我们再来看执行情况

执行结果

我们看到,NSInvocationOperation 和 NSBlockOperation是异步执行的,NSBlockOperation中的每一个Block也是异步执行且都在子线程中执行,每一个Block内部也依然是同步执行。

是不是简单好用又强大?

放入队列中的NSOperation对象不需要调用start方法,NSOPerationQueue会在『合适』的时机去自动调用

更简单的使用方式

除了上述的将NSOperation添加到队列中的使用方法外,NSOperationQueue提供了一个更加简单的方法,只需以下两行代码就能实现多线程调用

1    NSOperationQueue * queue = [[NSOperationQueue alloc]init];
2 [queue addOperationWithBlock:^{
3     //这里是你想做的操作
4 }];

你可以同时添加一个或这个多个Block来实现你的操作
怎么样,是不是简单的要死?
(原来这篇文章只需要看这两句就行了是嘛?????????????????????)

添加依赖关系

如果NSOperationQueue仅能做到这些,那我也不必大费周章了。
NSOperationQueue最吸引人的无疑是它的添加依赖的功能。
举个例子,假如A依赖于B,那么在B执行结束之前,A将永远不会执行
示例代码如下

 1 {
 2 NSInvocationOperation * op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation1) object:nil];
 3 NSInvocationOperation * op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation2) object:nil];
 4 NSOperationQueue * queue = [[NSOperationQueue alloc]init];
 5 [op2 addDependency:op1];
 6 [queue addOperation:op1];
 7 [queue addOperation:op2];
 8
 9 }
10 - (void)testNSInvocationOperation1
11 {
12 NSLog(@"我是op1  我在第%@个线程",[NSThread currentThread]);
13 }
14 - (void)testNSInvocationOperation2
15 {
16 NSLog(@"我是op2 我在第%@个线程",[NSThread currentThread]);
17 }

然后无论你运行多少次,得到的一定是这样的结果

必然结果

这就是依赖关系的好处,op2必定会在op1之后执行,这样会大大方便我们的流程控制。

使用依赖关系有三点需要注意
1.不要建立循环依赖,会造成死锁,原因同循环引用
2.使用依赖建议只使用NSInvocationOperation,NSInvocationOperation和NSBlockOperation混用会导致依赖关系无法正常实现。
3.依赖关系不光在同队列中生效,不同队列的NSOperation对象之前设置的依赖关系一样会生效

2016年03月29日11:16:00更新

之前放的代码有一点小小的问题 添加依赖的代码最好放到添加队列之前
前面说过,NSOperationQueue会在『合适』的时间自动去执行任务,因此你无法确定它到底何时执行,有可能前一秒添加的任务,在你这一秒准备添加依赖的时候就已经执行完了,就会出现依赖无效的假象。代码已更正,谢谢评论区各位提醒

设置优先级

每一个NSOperation的对象都一个queuePriority属性,表示队列优先级。它是一个枚举值,有这么几个等级可选

优先级可选等级

大家可以去设置试试,不过它并不总是起作用,目前我还没有找到原因。所以还是建议用依赖关系来控制流程。
如果有小伙伴知道怎么让优先级始终生效的办法,请告知我。。。

其他操作及注意事项

NSOperationQueue提供暂停和取消两种操作。
设置暂停只需要设置queue的suspended属性为YESNO即可
取消你可以选择调用某个NSOperation的cancle方法,也可以调用Queue的cancelAllOperations方法来取消全部线程

这里需要强调的是,所谓的暂停和取消并不会立即暂停或取消当前操作,而是不在调用新的NSOperation。

改变queue的maxConcurrentOperationCount可以设置最大并发数。
这里依然有两点需要注意
1.最大并发数是有上限的,即使你设置为100,它也不会超过其上限,而这个上限的数目也是由具体运行环境决定的
2.设置最大并发数一定要在NSOperationQueue初始化后立即设置,因为上面说过,被放到队列中的NSOperation对象是由队列自己决定何时执行的,有可能你这边一添加立马就被执行。因此要想让设置生效一定要在初始化后立即设置



结束语
到这里,NSOperation的知识我们已经介绍完毕,如果你尝试用一两次的话,你一定会爱上他。

时间: 2024-11-06 10:01:17

还在用GCD?来看看NSOperation吧的相关文章

[转]还在用GCD?来看看NSOperation吧

转自:http://www.jianshu.com/p/0c241a4918bf 在iOS开发中,谈到多线程,大家第一时间想到的一定是GCD.GCD固然是一套强大的多线程解决方案,能够解决绝大多数的多线程问题,但是他易于上手难于精通且到处是坑的特点也注定了想熟练使用它有一定的难度.而且很多人嘴上天天挂着GCD,实际上对它的实际应用也不甚了解.再者说,在现在的主流开发模式下,能用到多线程的绝大多数就是网络数据请求和网络图片加载,这两点上AFNetwork+SDWebImage已经能满足几乎所有的需

iOS多线程开发--NSThread NSOperation GCD

多线程 当用户播放音频.下载资源.进行图像处理时往往希望做这些事情的时候其他操作不会被中 断或者希望这些操作过程中更加顺畅.在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势必影响用户体验.早在单核处理器时期 就有多线程,这个时候多线程更多的用于解决线程阻塞造成的用户等待(通常是操作完UI后用户不再干涉,其他线程在等待队列中,CPU一旦空闲就继续执行, 不影响用户其他UI操作),其处理能力并没有明显的变化.如今无论是移动操作系统还是PC.服务器都是多核处理器,于是“并行

GCD / NSOperation

 GCD 与 NSOperation 的区别 ? 1. GCD 是基于 C 语言写的核心服务, 非常简单高效, 而 NSOperation 是基于 GCD 的一种封装,抽象出来的对象, 所以一般情况下对于任务的依赖和并发数没有要求的情况下, GCD 的效率更高, 开销更小 2. 依赖关系,NSOperation可以设置两个NSOperation之间的依赖,第二个任务依赖于第一个任务完成执行,GCD无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果 3. KV

iOS多线程 NSThread/GCD/NSOperationQueue

http://www.cnblogs.com/kenshincui/p/3983982.html iOS开发系列--并行开发其实很容易 2014-09-20 23:34 by KenshinCui, 9738 阅读, 19 评论, 收藏,  编辑 --多线程开发 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的,一个复杂的多步操作只能一步步按顺序逐个执行.改变这种

iOS多线程之NSOperation的使用

NSOperation对象的使用步骤 NSOperation对象描述了一个操作任务:NSOperationQueue对象描述了一个任务队列,相当于GCD的dispatch_queue_t NSOperation及NSOperationQueue可以任务是GCD之上的面向对象封装 GCD提供了更底层的控制,NSOperationQueue在GCD之上实现了一些方便的功能,这些功能对于开发者而言通常是最好最安全的选择 基本使用步骤 : 1)定义操作队列 2)定义操作 3)将操作添加到队列 提示: 一

GCD介绍(转)

GCD介绍(一): 基本概念和Dispatch Queue GCD提供很多超越传统多线程编程的优势: 易用: GCD比之thread跟简单易用.由于GCD基于work unit而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束.监视文件描述符.周期执行代码以及工作挂起等任务.基于block的血统导致它能极为简单得在不同代码作用域之间传递上下文. 效率: GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速.这关系到易用性:导致GCD易用的原因有一部

GCD基本概念和Dispatch Queue

什么是GCD? Grand Central Dispatch或者GCD,是?一套低层API,提供了?一种新的?方法来进?行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将 任务切分为多个单?一任务然后提交?至?工作队列来并发地或者串?行地执?行.GCD?比之NSOpertionQueue更底层更?高效,并且它不是Cocoa框架的?一部分. 除了代码的平?行执?行能?力,GCD还提供?高度集成的事件控制系统.可以设置句柄来响应?文件描述符.mach po

GCD: 基本概念和Dispatch Queue 【转】

什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行.GCD比之 NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分. 除了代码的平行执行能力,GCD还提供高度集成的事件控制系统.可以设置句柄来响应文件描述符.mach ports(Mach port 用于

GCD的一些用法

GCD是Grand Central Dispatch 的缩写. 即多线程优化技术. 它可以提供线程安全的队列,串行队列和并行队列,同步和异步执行任务.在队列中, 有很多回调块的执行单位, 完成一个任务后就回调块继续执行.GCD负责线程的创建,管理及释放,我们不用管理者一块. GCD队列特点: 1. 不是为了通常的数据存储而设计的 2. 它没有取消功能, 没有随机访问功能 3. 使用合理的数据结构来解决问题 4. 自动的线程创建和回收 5. 通过块来实现回调, 极大简化代码复杂度 GCD队列类型