GCD
GCD是最常用的管理并行代码和执行异步操作的Unix系统层的API。GCD构造和管理队列中的任务。
队列是按先进先出(FIFO)管理对象的数据结构。队列类似电影院的售票窗口,票的销售是谁先到谁先服务。在等待线前面的人先去买他们的门票,在其余的后抵达的人之前。队列在计算机科学中是相似的,因为第一个添加到队列的对象也是第一个从队列中删除的对象。
操作队列 NSOperationQueue
GCD是一个底层的C的API,它使开发人员能够并行地执行任务。操作队列,另一方面,是高度抽象的队列模型,是建立在GCD之上的。这意味着你可以并行执行任务就像GCD一样,但以面向对象的方式。简而言之,队列操作让编程更加简单。
不同于GCD,它们不按先进先出的顺序。下面是操作队列和调度队列的不同点:
1.不遵循先进先出:在操作队列中,你可以设置一个操作的执行优先级,你可以添加操作之间的依赖关系,这意味着你可以定义一些操作完成后才会执行其他操作。这就是为什么它们不遵循先进先出。
2.默认情况下,它们同时操作:然而你不能把它的类型改变成串行队列。通过使用操作之间的依赖关系,在操作队列还存在一个工作区来依次执行任务。
3.操作队列是类NSOperationQueue的实例,其任务封装在NSOperation的实例里。
NSOperation
是苹果提供的一套多线程解决方案,实际上NSOperation是基于GCD 更高一层的封装,但是比GCD 更简单易用、代码可读性更高。
需要配合NSOperationQueue 来实现多线程。 默认情况下,单独使用NSOperation时系统执行同步操作,并没有开辟新线程的能力,只有和配合NSOperationQueue 才能实现异步执行。
因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。NSOperation实现多线程的使用步骤分为三步:
- 创建任务:先将需要执行的操作封装到一个NSOperation对象中。
- 创建队列:创建NSOperationQueue对象。
- 将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中。
之后呢,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。
NSOperation是一个抽象类,它不能直接使用,所以你必须使用NSOperation子类。在iOS SDK里,我们提供两个NSOperation的具体子类。这些类可以直接使用,但你也可以继承NSOperation来创建自己的类来执行操作。我们可以直接使用的两个类:
1.NSBlockOperation——使用这个类来用一个或多个block初始化操作。操作本身可以包含多个块。当所有block被执行操作将被视为完成。
2.NSInvocationOperation——使用这个类来初始化一个操作,它包括指定对象的调用selector。
或者定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
NSOperationQueue
和GCD中的并发队列、串行队列不同的是,NSOperationQueue 一共有两种队列:主队列、其他队列。
其他队列中包含了串行、并发功能。
NSOperationQueue *queue = [NSOperationQueue mainQueue];//主队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];//其他队列
将任务加入到队列中
1.先创建任务,再将创建好的任务加入到创建好的队列中去
- (void)addOperation:(NSOperation *)op;
2.无需创建任务,在block中执行任务,直接将任务block加入到队列中
(void)addOperationWithBlock:(void (^)(void))block;
控制串行执行和并发执行的关键
最大并发数:maxConcurrentOperationCount
默认情况下是-1,表示不进行限制,默认为并发执行。
当为1时,进行串行执行。
当>1时,进行并发执行。
操作依赖
NSOperation和NSOperationQueue最吸引人的地方是它能添加操作之间的依赖关系。比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A。
- (void)addDependency
一些其他的方法:
- - (void)cancel; NSOperation提供的方法,可取消单个操作
- - (void)cancelAllOperations; NSOperationQueue提供的方法,可以取消队列的所有操作
- - (void)setSuspended:(BOOL)b; 可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列
- - (BOOL)isSuspended; 判断暂停状态
注意:
这里的暂停和取消并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作
- 暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
自定义子类:
使用main方法,不需要管理一些状态属性(如isExecuting 和 isFinished ),当main 方法返回的时候,这个operation 就结束了。 这种方式使用起来非常简单,main方法执行完就认为operation 结束了。所以一般可以用来执行同步任务。
如果你希望拥有更多的控制权,或者想在一个操作中可以执行异步任务,那么就重写start 方法。
在这种情况下,必须手动管理操作的状态,只有当发送isFinished 的kvo 消息时,才认为是operation 结束。
所以NSOperation的优势是什么?
1.首先,它们通过NSOperation类里的方法addDependency(op:NSOperation)支持依赖。当你需要开始一个依赖于其它操作执行的操作,你会需要NSOperation。
2.其次,你可以通过下面这些值设置属性queuePriority来改变执行优先级:
3.对于任何给定的队列,你可以取消一个特定的或所有的操作。操作可以在被添加到队列后被取消。取消是通过调用NSOperation类里的方法cancel()。当你取消任何操作的时候,我们有三个场景,其中一个会发生:
你的操作已经完成。在这种情况下,取消方法没有效果。
你的操作已经被执行。在这种情况下,系统不会强制操作代码停止,而是属性cancelled被置为true。
你的操作仍在队列中等待。在这种情况下,你的操作将不会被执行。
4.NSOperation有3个有用的布尔属性,finished、 cancelled和ready。一旦操作执行完成,finisher将被置为true。一旦操作被取消,cancelled将被置为true。一旦准备即将被执行,ready将被置为true。
5.任何NSOperation有一个选项来设置回调,一旦任务完成将会被调用。在NSOperation里,一旦属性finished被置为true,这个block将被调用。
原理探析:
demo:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configurationQueue];
LDNSOperation *operation = [[LDNSOperation alloc] init];
[self.operationQueue addOperation:operation];
[NSThread sleepForTimeInterval:3];
[operation cancel];
}
-(void)configurationQueue{
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 4;
}
LDNSOperation为NSOperation的子类,重写start方法:
-(void)start{
while (true) {
if(self.cancelled){
NSLog(@"已经取消");
return;
}
NSLog(@"start");
[NSThread sleepForTimeInterval:1];
}
}
1.
[self.operationQueue addOperation:operation];
添加一个未完成的NSOperation ,其实就是将NSOperation 添加到一个动态的数组中。
internal 就是一个内部类,指的是NSOperationQueue 。
使用了KVO手动通知,进行operations 和 operationCount 的改变通知。
完成的话,就从operations 中删除。
如果准备完成,就添加到waiting 中,等待被执行。
_execute 方法:
如果没有暂停,就从waiting 中取出第一个,并且删除waiting 中的这个数据。
添加isFinished 属性观察
如果是并发的话,就马上执行。
如果不是,使用detachNewThreadSelector来创建新的线程去执行start。
所有的线程都很忙,并且没有达到threadCount最大值时,会创建新的线程。
_execute 是一个执行队列,依次将等待队列里所有的operation 进行start 。
对于start 函数来讲,一个NSOperation 并没有新建一个线程。 依然操作在[NSTread currentThread]中。
现在来思考下,也就明白了为什么NSOperationQueue要有两种处理方式了,如果NSOperation支持并发,然后NSOperationQueue在为其分配线程,那就是线程里面又跑了一条线程,这样就很尴尬了,通过isConcurrent可以避免这种现象。