iOS开发之GCD使用总结

简介:
GCD是iOS的一种底层多线程机制,今天总结一下GCD的常用API和概念,希望对大家的 学习起到帮助作用。
   GCD队列的概念
  在多线程开发当中,程序员只要将想做的事情定义好,并追加到DispatchQueue(派发队列)当中就好了。
  派发队列分为两种,一种是串行队列(SerialDispatchQueue),一种是并行队列(ConcurrentDispatchQueue)。
  一个任务就是一个block,比如,将任务添加到队列中的代码是:
  1 dispatch_async(queue, block);
  当给queue添加多个任务时,如果queue是串行队列,则它们按顺序一个个执行,同时处理的任务只有一个。
  当queue是并行队列时,不论第一个任务是否结束,都会立刻开始执行后面的任务,也就是可以同时执行多个任务。
  但是并行执行的任务数量取决于XNU内核,是不可控的。比如,如果同时执行10个任务,那么10个任务并不是开启10个线程,线程会根据任务执行情况复用,由系统控制。
   获取队列
  系统提供了两个队列,一个是MainDispatchQueue,一个是GlobalDispatchQueue。
  前者会将任务插入主线程的RunLoop当中去执行,所以显然是个串行队列,我们可以使用它来更新UI。
  后者则是一个全局的并行队列,有高、默认、低和后台4个优先级。
  它们的获取方式如下:
  1 dispatch_queue_t queue = dispatch_get_main_queue();
  2
  3 dispatch queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY_DEFAULT, 0)
   执行异步任务
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2     dispatch_async(queue, ^{
  3         //...
  4     });
  这个代码片段直接在子线程里执行了一个任务块。使用GCD方式任务是立即开始执行的
  它不像操作队列那样可以手动启动,同样,缺点也是它的不可控性。
  令任务只执行一次
  1 + (id)shareInstance {
  2     static dispatch_once_t onceToken;
  3     dispatch_once(&onceToken, ^{
  4         _shareInstance = [[self alloc] init];
  5     });
  6 }
  这种只执行一次且线程安全的方式经常出现在单例构造器当中。
  任务组
  有时候,我们希望多个任务同时(在多个线程里)执行,再他们都完成之后,再执行其他的任务,
  于是可以建立一个分组,让多个任务形成一个组,下面的代码在组中多个任务都执行完毕之后再执行后续的任务:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2     dispatch_group_t group = dispatch_group_create();
  3
  4     dispatch_group_async(group, queue, ^{ NSLog(@"1"); });
  5     dispatch_group_async(group, queue, ^{ NSLog(@"2"); });
  6     dispatch_group_async(group, queue, ^{ NSLog(@"3"); });
  7     dispatch_group_async(group, queue, ^{ NSLog(@"4"); });
  8     dispatch_group_async(group, queue, ^{ NSLog(@"5"); });
  9
  10     dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"done"); }); 延迟执行任务
  1     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  2         //...
  3     });
  这段代码将会在10秒后将任务插入RunLoop当中。
  dispatch_asycn和dispatch_sync
  先前已经有过一个使用dispatch_async执行异步任务的一个例子,下面来看一段代码:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2
  3     dispatch_async(queue, ^{
  4         NSLog(@"1");
  5     });
  6
  7     NSLog(@"2");
  这段代码首先获取了全局队列,也就是说,dispatch_async当中的任务被丢到了另一个线程里去执行,async在这里的含义是,当当前线程给子线程分配了block当中的任务之后,当前线程会立即执行,并不会发生阻塞,也就是异步的。那么,输出结果不是12就是21,因为我们没法把控两个线程RunLoop里到底是怎么执行的。
  类似的,还有一个“同步”方法dispatch_sync,代码如下:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2
  3     dispatch_sync(queue, ^{
  4         NSLog(@"1");
  5     });
  6
  7     NSLog(@"2");
  这就意味着,当主线程将任务分给子线程后,主线程会等待子线程执行完毕,再继续执行自身的内容,那么结果显然就是12了。
  需要注意的一点是,这里用的是全局队列,那如果把dispatch_sync的队列换成主线程队列会怎么样呢:
  1     dispatch_queue_t queue = dispatch_get_main_queue();
  2     dispatch_sync(queue, ^{
  3         NSLog(@"1");
  4     });
  这段代码会发生死锁,因为:
  1.主线程通过dispatch_sync把block交给主队列后,会等待block里的任务结束再往下走自身的任务,
  2.而队列是先进先出的,block里的任务也在等待主队列当中排在它之前的任务都执行完了再走自己。
  这种循环等待就形成了死锁。所以在主线程当中使用dispatch_sync将任务加到主队列是不可取的。
   创建队列
  我们可以使用系统提供的函数获取主串行队列和全局并行队列,当然也可以自己手动创建串行和并行队列,代码为:
  1     dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_SERIAL);
  2     dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_CONCURRENT);
  在MRC下,手动创建的队列是需要释放的
  1     dispatch_release(myConcurrentDispatchQueue);
  手动创建的队列和默认优先级全局队列优先级等同,如果需要修改队列的优先级,需要:
  1     dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_CONCURRENT);
  2     dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
  3     dispatch_set_target_queue(myConcurrentDispatchQueue, targetQueue);
  上面的代码修改队列的优先级为后台级别,即与默认的后台优先级的全局队列等同。
   串行、并行队列与读写安全性
  在向串行队列(SerialDispatchQueue)当中加入多个block任务后,一次只能同时执行一个block,如果生成了n个串行队列,并且向每个队列当中都添加了任务,那么系统就会启动n个线程来同时执行这些任务。
  对于串行队列,正确的使用时机,是在需要解决数据/文件竞争问题时使用它。比如,我们可以令多个任务同时访问一块数据,这样会出现冲突,也可以把每个操作都加入到一个串行队列当中,因为串行队列一次只能执行一个线程的任务,所以不会出现冲突。
  但是考虑到串行队列会因为上下文切换而拖慢系统性能,所以我们还是很期望采用并行队列的,来看下面的示例代码:
1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2     dispatch_async(queue, ^{
3         //数据读取
4     });
5     dispatch_async(queue, ^{
6         //数据读取2
7     });
8     dispatch_async(queue, ^{
9         //数据写入
10     });
11     dispatch_async(queue, ^{
12         //数据读取3
13     });
14     dispatch_async(queue, ^{
15         //数据读取4
16     });
  显然,这5个操作的执行顺序是我们无法预期的,我们希望在读取1和读取2执行结束后,再执行写入,写入完成后再执行读取3和读取4。
  为了实现这个效果,这里可以使用GCD的另一个API:
  1     dispatch_barrier_async(queue, ^{
  2         //数据写入
  3     });
  这样就保证的写入操作的并发安全性。
  对于没有数据竞争的并行操作,则可以使用并行队列(CONCURRENT)来实现。
JOIN行为
  CGD利用dispatch_group_wait来实现多个操作的join行为,代码如下:
1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2     dispatch_group_t group = dispatch_group_create();
3
4     dispatch_group_async(group, queue, ^{
5         sleep(0.5);
6         NSLog(@"1");
7     });
8     dispatch_group_async(group, queue, ^{
9         sleep(1.5);
10         NSLog(@"2");
11     });
12     dispatch_group_async(group, queue, ^{
13         sleep(2.5);
14         NSLog(@"3");
15     });
16
17     NSLog(@"aaaaa");
18
19     dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC);
20     if (dispatch_group_wait(group, time) == 0) {
21         NSLog(@"已经全部执行完毕");
22     }
23     else {
24         NSLog(@"没有执行完毕");
25     }
26
27     NSLog(@"bbbbb");
  这里起了3个异步线程放在一个组里,之后通过dispatch_time_t创建了一个超时时间(2秒),程序之后行,立即输出了aaaaa,这是主线程输出的,当遇到dispatch_group_wait时,主线程会被挂起,等待2秒,在等待的过程当中,子线程分别输出了1和2,2秒时间达到后,主线程发现组里的任务并没有全部结束,然后输出了bbbbb。
  在这里,如果超时时间设置得比较长(比如5秒),那么会在2.5秒时第三个任务结束后,立即输出bbbbb,也就是说,当组中的任务全部执行完毕时,主线程就不再被阻塞了。
  如果希望永久等待下去,时间可以设置为DISPATCH_TIME_FOREVER。
   并行循环
  类似于C#的PLINQ,OC也可以让循环并行执行,在GCD当中有一个dispatch_apply函数:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2     dispatch_apply(20, queue, ^(size_t i) {
  3         NSLog(@"%lu", i);
  4     });
  这段代码让i并行循环了20次,如果内部处理的是一个数组,就可以实现对数组的并行循环了,它的内部是dispatch_sync的同步操作,所以在执行这个循环的过程当中,当前线程会被阻塞。
   暂停和恢复
  使用dispatch_suspend(queue)可以暂停队列中任务的执行,使用dispatch_result(queue)可以继续执行被暂停的队列。


最新内容请见作者的GitHub页:http://qaseven.github.io/
相关文章
|
1月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
88 3
|
3月前
|
存储 iOS开发
iOS 开发,如何进行应用的本地化(Localization)?
iOS 开发,如何进行应用的本地化(Localization)?
122 2
|
3月前
|
存储 数据建模 数据库
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
39 0
|
3月前
|
安全 编译器 Swift
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
93 2
|
3月前
|
API 开发工具 iOS开发
iOS 开发高效率工具包:10 大必备工具
iOS 开发高效率工具包:10 大必备工具
48 1
|
7天前
|
调度 iOS开发 开发者
iOS 中的并发编程模式:理解 GCD 和 Operation Queues
【4月更文挑战第19天】 在现代 iOS 应用开发中,有效地管理线程和并发是至关重要的。为了确保用户界面的流畅性和后台任务的高效运行,开发者需要掌握并发编程技术。本文深入探讨了两种主要的并发模式:Grand Central Dispatch (GCD) 和 Operation Queues。我们将分析它们的工作原理,比较它们的特点,并通过实际代码示例说明如何在 iOS 应用中实现高效的并发处理。通过本文,读者将获得在 iOS 平台上进行多线程开发的实用知识,以及如何根据不同的应用场景选择最合适的并发工具。
|
8天前
|
API 定位技术 iOS开发
IOS开发基础知识:什么是 Cocoa Touch?它在 iOS 开发中的作用是什么?
【4月更文挑战第18天】**Cocoa Touch** 是iOS和Mac OS X应用的核心框架,包含面向对象库、运行时系统和触摸优化工具。它提供Mac验证的开发模式,强调触控接口和性能,涵盖3D图形、音频、网络及设备访问API,如相机和GPS。是构建高效iOS应用的基础,对开发者至关重要。
12 0
|
23天前
|
开发工具 Swift iOS开发
利用SwiftUI构建动态用户界面:iOS开发新范式
【4月更文挑战第3天】 随着苹果不断推进其软件开发工具的边界,SwiftUI作为一种新兴的编程框架,已经逐渐成为iOS开发者的新宠。不同于传统的UIKit,SwiftUI通过声明式语法和强大的功能组合,为创建动态且响应式的用户界面提供了一种更加简洁高效的方式。本文将深入探讨如何利用SwiftUI技术构建具有高度自定义能力和响应性的用户界面,并展示其在现代iOS应用开发中的优势和潜力。
|
2月前
|
监控 API Swift
用Swift开发iOS平台上的上网行为管理监控软件
在当今数字化时代,随着智能手机的普及,人们对于网络的依赖日益增加。然而,对于一些特定场景,如家庭、学校或者企业,对于iOS设备上的网络行为进行管理和监控显得尤为重要。为了满足这一需求,我们可以利用Swift语言开发一款iOS平台上的上网行为管理监控软件。
200 2
|
3月前
|
数据可视化 iOS开发
iOS 开发,什么是 Interface Builder(IB)?如何使用 IB 构建用户界面?
iOS 开发,什么是 Interface Builder(IB)?如何使用 IB 构建用户界面?
40 4