GCD(Grand Central Dispatch):基于C语言,属于更底层的多线程技术,所以在效率上的优势比较明显;依赖于Block执行任务也使得开发上变得更简单,方便开发者看到任务的上下文。
NSOperation和NSOperationQueue:其实出现得比GCD更早,但是GCD出现以后苹果在GCD的基础上对NSOperation进行了重写,使其对象化,符合了大众开发者的习惯。作为更高层的技术,NSOperation在处理依赖关系、控制各种操作、控制并发数量等方面都有一定的优势。(tips:NSOperation对于操作的暂停和取消并不是指当前操作可以马上停止和取消,而是当前操作结束以后会停止之后的所有操作。以前一度认为GCD不可以暂停和取消任务,后来发现并不是的,iOS8以后可以使用dispatch_block_cancel,开发者也可以自己用额外代码实现)
博主作为一个四年多的老iOS程序开发,用到GCD和NSOperation的场景太多了,当然有人会说多线程技术也会用到NSThread,为什么不提及,因为NSThread需要自己控制线程的生命周期,用到的场景实在太少了,这里就不再赘述了。至于NSThread的原理和使用大家可以去网上查找。网上对于GCD和NSOperation的介绍和对比可以找出大堆文章,但是就博主自身而言,对实际开发遇到的难题能起到帮助的少之又少。经常会遇到的就是这篇文章推荐使用GCD,那篇文章又推荐使用NSOperation,导致开发者在实际开发中变得混乱,严重影响开发效率。
其实大部分情况下不管选择哪种技术都能达到预期的效果,无非是代码量和开发者个人理解的问题。就个人而言,以下几种情况可以优先考虑GCD:
(1)任务比较简单,需要集中代码的情况
(2)多个任务相互独立,不用考虑依赖关系和优先级关系的情况
(3)对代码执行效率要求比较高的情况
以下几种情况可以有限考虑NSOperation/NSOperationQueue:
(1)任务比较复杂,代码量庞大,集中代码会导致后期维护修改困难的情况
(2)几个任务之间有联系,相互依赖或者要求明确的优先级关系的情况
(3)需要自己维护任务,控制任务的暂停、取消的情况
(4)需要监听任务对象属性的情况(如监听NSOperation的isCancelled来判断任务是否取消)
话不多说,直接写代码,这里我们为了方便只用NSBlockOperation来对比
一、同步多任务,用sleep()模拟任务耗时,可以看到task1、task2、task3有序执行
GCD(同步任务使用并发和串行队列效果一致,只能在主队列中顺序执行)
//并发队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//串行队列
//dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
sleep(2);
NSLog(@"task1 %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep(1);
NSLog(@"task2 %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep(3);
NSLog(@"task3 %@", [NSThread currentThread]);
});
NSOperation/NSOperationQueue(直接使用start()开始任务默认在主线程中顺序执行,或者使用NSOperationQueue设置队列并发数为1达到同步执行效果)
/// NSOperation同步执行任务
- (void)operationSync {
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"task1 %@", [NSThread currentThread]);
}];
[operation1 start];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(2);
NSLog(@"task2 %@", [NSThread currentThread]);
}];
[operation2 start];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"task3 %@", [NSThread currentThread]);
}];
[operation3 start];
}
/// NSOperation和NSOperationQueue实现同步任务效果
- (void)operationQueueSync {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置并发数为1实现同步效果
queue.maxConcurrentOperationCount = 1;
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
sleep(2);
NSLog(@"task1 %@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"task2 %@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"task3 %@", [NSThread currentThread]);
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
}
二、异步多任务
GCD(使用栅栏和GCD队列组的情况)
/**
栅栏处理异步多任务,task1、task2、task3顺序不定,task4、task5顺序不定
*/
- (void)barrierAsync {
//并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//串行队列:若为串行队列,任务永远按顺序执行
//dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"task1 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task2 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task3 %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"task barrier %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task4 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task 5 %@", [NSThread currentThread]);
});
}
/**
GCD队列组处理多任务先执行,再进行后面任务,task1、task2、task3顺序不定,三个完成后执行task last
*/
- (void)groupQueue {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task1 %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2 %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3 %@", [NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"task last %@", [NSThread currentThread]);
});
}
NSOperation/NSOperationQueue(使用依赖控制task1、task2、task3完成以后再执行lastTask,前三者顺序不定)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *lastOperation = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"@"lastTask %@", [NSThread currentThread]");
}];
NSBlockOperation *requestOperation1 = [NSBlockOperation blockOperationWithBlock:^{
sleep(4);
NSLog(@"@"task1 %@", [NSThread currentThread]");
}];
NSBlockOperation *requestOperation2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(2);
NSLog(@"@"task2 %@", [NSThread currentThread]");
}];
NSBlockOperation *requestOperation3 = [NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"@"task3 %@", [NSThread currentThread]");
}];
[lastOperation addDependency:requestOperation1];
[lastOperation addDependency:requestOperation2];
[lastOperation addDependency:requestOperation3];
[queue addOperation:lastOperation];
[queue addOperation:requestOperation1];
[queue addOperation:requestOperation2];
[queue addOperation:requestOperation3];
三、处理多个异步任务中含异步操作的情况(多个网络请求以后刷新UI)
这是实际开发中经常遇到的问题,多个网络请求获取不同的数据,但是每个接口的数据都是必须的,只有拿到所有的数据才能进行下面的操作,然而接口请求又是异步的。
GCD(GCD队列组的enter和leave,GCD队列组信号量)
/**
GCD组多个异步网络请求返回数据以后再进行主线程的UI操作 enter和leave要成对出现
*/
- (void)groupAsyncRequest {
dispatch_queue_t queue = dispatch_queue_create("asyncRequest", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
//模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"task1 %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
});
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
//模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"task2 %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
});
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
//模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"task3 %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task UI %@", [NSThread currentThread]);
});
});
}
/**
GCD队列组用信号量处理多个异步网络请求
*/
- (void)semaphoreRequest {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("semaphore", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//value表示可访问新生成总信号量个数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"task1 %@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, queue, ^{
//value表示可访问新生成总信号量个数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"task2 %@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, queue, ^{
//value表示可访问新生成总信号量个数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//模拟网络请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"task3, %@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task UI %@", [NSThread currentThread]);
});
});
}
NSOperation/NSOperationQueue
/// 网络请求数据返回以后再将相应队列任务加入到队列中
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *lastOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"last task");
}];
NSBlockOperation *requestOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1");
}];
NSBlockOperation *requestOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2");
}];
NSBlockOperation *requestOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task3");
}];
[lastOperation addDependency:requestOperation1];
[lastOperation addDependency:requestOperation2];
[lastOperation addDependency:requestOperation3];
[queue addOperation:lastOperation];
[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:@"https://www.csdn.net"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[queue addOperation:requestOperation1];
}];
[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[queue addOperation:requestOperation2];
}];
[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:@"https://github.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[queue addOperation:requestOperation3];
}];
}
总的来说,不管是什么情况,我们都可以选择任何一种多线程技术。多数开发者是根据自身的开发习惯选择,比如博主就会在多数情况下选择GCD。之前也问过一些大神朋友,为什么很多国外论坛的朋友会选择NSOperation,得到的答案几乎是一样的,并不是谁优谁劣,需要自己取舍。