
在iOS开发中,谈到多线程,大家第一时间想到的一定是GCD。GCD固然是一套强大的多线程解决方案,能够解决绝大多数的多线程问题,但是他易于上手难于精通且到处是坑的特点也注定了想熟练使用它有一定的难度。而且很多人嘴上天天挂着GCD,实际上对它的实际应用也不甚了解。
再者说,在现在的主流开发模式下,能用到多线程的绝大多数就是网络数据请求和网络图片加载,这两点上AFNetwork+SDWebImage已经能满足几乎所有的需求。而剩下的一小部分,简单好用的NSOperation无疑是比GCD更有优势的。
因此,如果你还是坚持『GCD大法好』,那看到这里就不必再看了。如果你想试一试更简单的方法,那就随我来吧。
什么是NSOperation?
和GCD一样,NSOperation也是苹果提供给我们的一套多线程解决方案。实际上它也是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。
NSOperation是一个抽象基类,基本没有什么实际使用价值。我们使用最多的是系统封装好的NSInvocationOperation
和NSBlockOperation
。
不过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是可以自定义的。如果NSInvocationOperation
和NSBlockOperation
无法满足你的需求,你可以选择自定义一个NSOperation。
经过上面的分析,我们发现,系统提供的两种NSOperation是一定满足不了我们的需求的。
那我们是不是需要自定义一个NSOperation呢?
答案是,不需要。
自定义NSOperation并不难,但是依然要写不少代码,这违背了我们简单实现多线程的初衷。况且,接下来我会介绍我们今天真正的主角--NSOperationQueue。所以,我打算直接跳过这一个环节。
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来实现你的操作
怎么样,是不是简单的要死?
(原来这篇文章只需要看这两句就行了是嘛?