还在用GCD?来看看NSOperation吧

2016年03月29日10:42:36更新

感谢@皮特尔 的提醒


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


什么是NSOperation?

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

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

NSInvocationOperation的使用方式和给Button添加事件比较相似,需要一个对象和一个Selector。使用方法非常简单。
我们先来写一个方法
- (void)testNSOperation
{
NSLog(@"我在第%@个线程",[NSThread currentThread]);
}
然后调用它

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

得到这样的执行结果

执行结果

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

使用NSBlockOperation
  • 终于到了我们今天的第一个重点
    NSBlockOperation也是NSOperation的子类,支持并发的实行一个或多个block,使用起来简单又方便
    执行以下代码
    NSBlockOperation * blockOperation = [[NSBlockOperation
    blockOperationWithBlock:^{
    NSLog(@"1在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
    NSLog(@"2在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
    NSLog(@"3在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
    NSLog(@"4在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
    NSLog(@"5在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
    NSLog(@"6在第%@个线程",[NSThread currentThread]);
    }];
    这里我们多执行两次并比较结果
第一次执行的结果
第二次执行的结果
第三次执行的结果
  • 通过三次不同结果的比较,我们可以看到,NSBlockOperation确实实现了多线程。但是我们可以看到,它并非是将所有的block都放到放到了子线程中。通过上面的打印记录我们可以发现,它会优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,但最大并发数为4(包括主线程在内)。如果block数量大于了4,那么剩下的Block就会等待某个线程空闲下来之后被分配到该线程,且依然是优先分配到主线程。
  • 另外,同一个block中的代码是同步执行的

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

 NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"1在第%@个线程",[NSThread currentThread]);
    NSLog(@"1haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"2在第%@个线程",[NSThread currentThread]);
    NSLog(@"2haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"3在第%@个线程",[NSThread currentThread]);
    NSLog(@"3haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"4在第%@个线程",[NSThread currentThread]);
    NSLog(@"4haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"5在第%@个线程",[NSThread currentThread]);
    NSLog(@"5haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"6在第%@个线程",[NSThread currentThread]);
    NSLog(@"6haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"7在第%@个线程",[NSThread currentThread]);
    NSLog(@"7haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"8在第%@个线程",[NSThread currentThread]);
    NSLog(@"8haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"9在第%@个线程",[NSThread currentThread]);
    NSLog(@"9haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"10在第%@个线程",[NSThread currentThread]);
    NSLog(@"10haha");
}];

[blockOperation start];

然后我们看一下执行结果

执行结果

]

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

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

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

自定义NSOperation

是的,你没看错,NSOperation是可以自定义的。如果NSInvocationOperationNSBlockOperation无法满足你的需求,你可以选择自定义一个NSOperation。
经过上面的分析,我们发现,系统提供的两种NSOperation是一定满足不了我们的需求的。
那我们是不是需要自定义一个NSOperation呢?
答案是,不需要。
自定义NSOperation并不难,但是依然要写不少代码,这违背了我们简单实现多线程的初衷。况且,接下来我会介绍我们今天真正的主角--NSOperationQueue。所以,我打算直接跳过这一个环节。
如果确实有同学需要的话,可以私信我。。。 如果很多人需要的话。。 我会额外写一篇。。。
(读者:你TM不讲还这么多废话(╯‵□′)╯︵┻━┻)

NSOPerationQueue
简单使用

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

//依然调用上面的那个方法
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];

NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:invo];

看一下执行结果

执行结果

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

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

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

然后我们再来看执行情况

执行结果

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

是不是简单好用又强大?

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

更简单的使用方式

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

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

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

添加依赖关系

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

{
NSInvocationOperation * op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation1) object:nil];
NSInvocationOperation * op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation2) object:nil];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[op2 addDependency:op1];
[queue addOperation:op1];
[queue addOperation:op2];

}
- (void)testNSInvocationOperation1
{
NSLog(@"我是op1  我在第%@个线程",[NSThread currentThread]);
}
- (void)testNSInvocationOperation2
{
NSLog(@"我是op2 我在第%@个线程",[NSThread currentThread]);
}

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

必然结果

这就是依赖关系的好处,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的知识我们已经介绍完毕,如果你尝试用一两次的话,你一定会爱上他。

作者水平有限,不正确的地方请指出

如果我的文章对你有帮助,请点赞或评论

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容