GCD 和 NSOperation都是iOS多线程技术,在现在的实现中NSOperation还是基于GCD的封装。GCD是C-based API
,把一个任务单元封装在一个block中,可以在c、c++中使用;NSOperation是Cocoa提供的,是一个objective-c抽象类,使用时你要继承它或直接使用系统提供的NSInvocationOperation
和NSBlockOperation
类。
接下来我会分别对GCD和NSOperation进行介绍:
GCD
Dispatch Queues
说到GCD肯定要说到dispatch queues
,因为你每次使用GCD提交任务时必须指定一个queue。dispatch queues
可以说是GCD的核心,它控制着你提交的任务是串行还是并行。
首先,dispatch queue
是一个队列,队列的特点就是FIFO(First In, First Out)。这意味着你按怎样顺序提交任务,它就按什么顺序开始这些任务。
其次,dispatch queue
主要分为两大种类:Serial(串行)
和Concurrent(并发)
。Serial
意味着队列中一次只能运行一个任务,必须等这个任务完了,才能开始下一任务;Concurrent
意味着可以并发运行队列中多个任务,不必等一个任务完就可以开始下个任务。不过这两个都有一个共同点,就是任务开始的顺序还是要跟加入顺序一样。
最后,介绍下创建或获取这两种queue的方法。
1.获取或创建Serial queue
:
1 | //自己创建一个Serial queue |
2.获取或创建Concurrent queue
,系统默认按不同优先级为我们创建了4个Concurrent queues
。iOS 5后,也可以自己指定DISPATCH_QUEUE_CONCURRENT
创建自己的concurrent queue
1 | //获取系统的concurrent queue |
默认的四个concurrent queue
优先级
1 | DISPATCH_QUEUE_PRIORITY_HIGH |
并发遍历一个数组
我们可以使用dispatch_apply
并发遍历一个数组,这在一些场景还是非常有用的
1 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
dispatch_apply
会生成count个block,加入queue队列,并发执行,当所有block都执行完,dispatch_apply
才会返回,所以dispatch_apply
也是个同步操作。
任务同步(依赖)
比如有三个任务A、B、C,C任务得等A、B都执行完才能开始,而A、B之间倒没这种要求,这样C就是依赖于A、B,或我们先并发执行A、B,然后得在一个地方,同步A、B任务(等它们都执行完),再开始C任务。这时用dispatch_group_t
, dispatch_group_async
和 dispatch_group_notify
可以满足我们需求
1 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
使用 Dispatch Semaphores 限制资源访问
系统的一些资源是有限的,比如I/O资源,这时可以使用dispatch semaphores
限制最大访问数量
1 | // Create the semaphore, specifying the initial pool size |
同步共享资源
我们有时会有希望同一个时间只有一个线程访问同一共享资源需求,这时可以使用Serail queue
。当然,在创建dispatch semaphore
时传入1也可以达到该目的。
1 | dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL); |
NSOperation
状态
一个NSOperation有多种状态,并且支持KVO
1 | isCancelled |
ready —> executing —> finished 是常见的状态路径
使用NSOperation
有三种方法可以把任务封装到NSOperation
方法
1.通过NSInvocationOperation
1 | @implementation MyCustomClass |
2.通过NSBlockOperation
1 | NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{ |
NSBlockOperation
可以添加多个block,一般来说这些block是并发执行的,只有这些block都执行完,这个operation才算finished
。如果想让这些block串行执行,可以创建一个NSOperationQueue
,然后为underlyingQueue
赋值一个串行的dispatch_queue_t
(不能是dispatch_get_main_queue
),最后把该operation添加到该NSOperationQueue。看起来很麻烦吧,所以不推荐这种做法来做串行。
3.自定义NSOperation
需要继承NSOperation
,至少实现两个函数:自定义初始化函数,main函数。NSOperation可以分为两个种类:异步和非异步。异步就是还起了个线程处理任务;非异步不创建新线程。下面是一个自定义异步operation的例子。
1 | @interface MyOperation : NSOperation { |
使用start启动一个operation
要注意观察isReady是否为YES,如果是NO,说明该operation的依赖还没执行完。默认start会在当前调用start的线程处理任务。
1 | - (BOOL)performOperation:(NSOperation*)anOp |
Operation Queues
我们可以通过将一个operation添加到一个NSOperationQueue
,让NSOperationQueue
自动管理operation。NSOperationQueue
会检查operation isReady,如果 isReady==YES
,则在适当时候分配线程start这个operation。所以用NSOperationQueue来启动线程还是有很多好处,推荐用这种方法。
1 | NSOperationQueue* aQueue = [[NSOperationQueue alloc] init]; |
注意:NSOperationQueue并不是按FIFO启动operation,它会考虑多种因素:优先级,isReady,提交顺序。所以不要用NSOperation来做串行,可以试着用依赖链,下面会介绍。
NSOperationQueue还支持最大并发数量:maxConcurrentOperationCount
取消operation
可以直接调用 cancel
函数。operation内部会做一些清理工作,最后把 cancelled和finished置为YES。如果在NSOperationQueue中,queue会自动把finished==YES的operation移除。
优先级(NSOperationQueuePriority)
我们可以为每个operation添加优先级。优先级越高,意味着有机会越快启动。
1 | typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { |
服务质量(Quality of Service)
服务质量等级越高,意味着你可以你可以获得更多的资源(cpu,内存、网络、磁盘),从而有更快的执行速度。
1 | typedef NS_ENUM(NSInteger, NSQualityOfService) { |
上面等级重上往下降低,不过NSQualityOfServiceDefault不确定,可能介于UserInitiated和Utility之间。
1 | NSOperation *backgroundOperation = [[NSOperation alloc] init]; |
任务依赖
NSOperation可以比GCD更方便使用依赖
1 | NSOperation *networkingOperation = ... |
resizingOperation只有在networkOperation执行完后才会启动。
completionBlock
有时任务完成后,可能要执行一些回调,这时completionBlock就可以派上用场
1 | NSOperation *operation = ...; |
GCD 和 NSOperation 怎么选择
这个问题其实怎么方便怎么来,下面是网上一些人经验。
优先考虑NSOperation情况:
- 任务之间有依赖关系
- 限制最大可执行任务数量
- 任务可能被取消
- 需要执行回调
优先考虑GCD情况:
- 任务只是简单block提交
- 任务之间需要复杂block嵌套
- 任务需要非常频繁提交(因为NSOperation是对象,频繁创建也很耗
CPU和内存)
ps:
Concurrency Programming Guide
NSOperation
iOS 多线程开发之OperationQueue(二)NSOperation VS GCD