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/

时间: 2024-12-09 12:52:34

iOS开发之GCD使用总结的相关文章

iOS开发之UITableView与UISearchController实现搜索及上拉加载,下拉刷新实例代码_IOS

废话不多说了,直接给大家贴代码了. 具体代码如下所示: #import "ViewController.h" #import "TuanGouModel.h" #import "TuanGouTableViewCell.h" #define kDeviceWidth [UIScreen mainScreen].bounds.size.width #define kDeviceHeight [UIScreen mainScreen].bounds.

iOS开发之UIKeyboardTypeNumberPad数字键盘自定义按键_IOS

最近做一个搜索用户的功能,这里使用了UISearchBar.由于搜索的方式只有手机号码,所以这里的键盘要限制为数字输入,可以这么做: self.searchBar.keyboardType = UIKeyboardTypeNumberPad;如果使用的不是搜索框而是textField输入框,可以设置textField的键盘属性来展示 self.textField.keyboardType = UIKeyboardTypeNumberPad;监听事件如下所示即可. 但是这里有个问题,就是数字键盘上

ios开发之Swift - 点击状态栏使tableView返回顶部(附:状态栏点击事件响应)

1,当页面上只有一个scrollView,点击状态栏scrollView会自动滚动到顶部   比如页面上只有一个表格(UITableView),当点击顶部状态条后,表格会像QQ.微信联系人列表那样回到最上面.   这个是iOS系统默认就有的.   开发之Swift - 点击状态栏使tableView返回顶部(附:状态栏点击事件响应)-swift ios开发教程"> 2,当页面上有多个scrollView,点击状态栏时,视图都不会滚动 这时我们需要把不需要滚动的 scrollView 的 s

ios开发之Swift使用AirPrint进行打印

使用 AirPrint 可以轻松地从 iOS 和 OS X app 中传输无损照片和文稿打印.当然,打印机也要支持AirPrint 技术才行.下面通过样例演示如何在App中使用 Airprint进行打印.    1,打印机模拟器(Printer Simulator)下载 如果没有支持AirPrint的打印机也没关系,苹果提供了个虚拟打印机,地址:https://developer.apple.com/downloads (1)下载里面的 Hardware IO Tools 开发之Swift使用A

ios开发之Swift闭包使用示例

什么是闭包? 闭包是指可以包含自由(未绑定到特定对象)变量的代码块:这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量). "闭包" 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域). 在Swift中,Swift的闭包跟OC中的Block很像,OC中的Block类似于匿名函数,闭包用来定义函数. 无论是OC中的Block还是Swift中的闭

ios开发之Swfit使用自定义的UIRefreshControl下拉刷新界面

默认 UIRefreshControl 下拉刷新界面是一个菊花进度条+一段描述文字,略显单调.其实我们可以使用自己创建的界面视图,方便我们实现各种效果.比如添加个动态图片,添加个动画效果什么的.   1,下面演示如何使用自定义的下拉刷新界面,效果图如下:   (1)随着下拉,界面透明度从0开始慢慢显示出来     开发之Swfit使用自定义的UIRefreshControl下拉刷新界面-uirefreshcontrol下拉">         (2)开始刷新时,文字会有跑马灯效果(字体逐个

ios开发之Swift UIPasteboard剪贴板的使用详解(复制、粘贴文字和图片)

UITextField.UITextView组件系统原生就支持文字的复制,但有时我们需要让其他的一些组件也能实现复制功能,比如点击复制UILabel上的文字.UIImageView中的图片.UITableView里单元格的内容.或者点击按钮把文字或图片自动复制到粘贴板中等等. 这些我们借助 UIPasteboard 就可以实现. 一,将内容写入到剪贴板中 1,复制字符串 UIPasteboard.generalPasteboard().string = "欢迎访问 hangge.com"

ios开发之Swift二维码QRCode的读取(从图片读取 ,或通过摄像头扫描)

1,直接读取图片中的二维码 使用 CIDetector 可以很方便的检测并读取二维码.下面是一个从 UIImage 中读取二维码的样例,我们要把图片上所有的二维码信息都打印出来. 开发之Swift二维码QRCode的读取(从图片读取 ,或通过摄像头扫描)-qrcode 读取二维码">    代码如下 复制代码 import UIKit   class ViewController: UIViewController {       override func viewDidLoad() {

ios开发之在UIView上使用自定义曲线绘制复杂图形(贝塞尔曲线)

有时我们需要绘制一个不规则路径的图形,里面可能包含直线或者曲线,这时就可以使用 UIBezierPath 来实现.   UIBezierPath类可以表示任何能够用Bezier曲线定义的形状,我们可以创建自己的自定义曲线.完成操作后,可以像其他路径一样,使用所得到的UIBezierPath对象进行填充和描边.   1,下面演示使用UIBezierPath绘制一个不规则图形: (1)画笔移动到矩形区域左上角 (2)从笔的当前位置向右上角的点画一条直线 (3)从笔的当前位置向左下角的点画一条直线 (