GCD 开发详情
目录
一、简介
二、dispatch Queue - 队列
三、dispatch Groups - 组
四、dispatch Semaphores - 信号量
五、dispatch Barriers - 障碍
六、dispatch sources - 系统源
七、dispatch I/O - I/O
八、总结
一、简介
GCD 的全称是 Grand Centre Dispatch 是一个强大的任务编程管理工具。通过GCD你可以同步或者异步地执行block、function。
二、dispatch Queue - 队列
queue是指先进先出的列表,是blocks的执行环境。这个环境可以是单线程的也可以使多线程这个取决于你创建的queue类型。如果是serial queue(串行队列)队列的话,queue内部是以单线程运行。被添加进去的blocks,按照它们被添加进去的顺序串行地执行。
如果是concurrent queue(并行队列),queue的内部线程个数由被添加的block执行的任务和当前系统有关。一般来说线程是会共用的,即是当一个block用完一个线程后,这个线程不会立马销毁会留着备用。这样就减少了创建线程的系统消耗。
我们下面看看怎么用queue相关的API
1. 创建queue
有两个创建queue的方法
一个是通过dispatch_queue_create手动创建queue,这个方法可以创建串行queue、并行queue
函数定义:
dispatch_queue_t dispatch_queue_create( const char *label dispatch_queue_attr_t attr);
参数 label 是指这个queue名字
参数 attr 可以指出需要创建的是什么类型的queue这里有两个值可选
DISPATCH_QUEUE_SERIAL ( NULL ) 串行queue
DISPATCH_QUEUE_CONCURRENT 并行queue 创建一个串行queue
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
另一个是通过 dispatch_get_global_queue 获取系统默认的queue,这个方法返回queue是并行的
dispatch_queue_t dispatch_get_global_queue( long identifier, unsigned long flags); 参数 identifier 用来指明queue的运行的优先级,可以取下值:
DISPATCH_QUEUE_PRIORITY_HIGH 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
参数 flags 保留
另外还可以通过 dispatch_get_main_queue 来获取mian queue。mian queue 是执行在主线程上的一个queue,它与消息循环交错的运行。
通过调用 dispatch_get_current_queue 可以返回当前的queue。在一个被提交的block的内部调用 dispatch_get_current_queue 函数返回的是运行这个block的queue,在block之外调用返回的是main queue。
2. 把block添加到queue运行
GCD也提供也支持把一个function添加到queue 的api,这个api的特点是多了后缀_f,比如的dispatch_async/dispatch_async_f
一个block只有被提交到queue后才能被执行,一旦block被提交到queue就无法取消,而且我们是无法得知block执行的状态。所以在一些提交任务后需要取消的场景我们可以用NSOperation。
异步执行block
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial, ^{
NSLog(@"from queue");
}); dispatch_async 异步的把一个block提交到queue,立即返回
dispatch_sync 同步的把一个block提交到queue,阻塞当前线程直到block执行完成 void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_after 在指定的时间到达时异步的提交block到queue执行。
when 参数是指延迟时间,是一个dispatch time 类型,可以通过dispatch_time、dispatch_walltime这两个函数生成
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
dispatch_once 在app的生命周期里只执行一次,常用于创建单例
3.void dispatch_set_target_queue( dispatch_object_t object, dispatch_queue_t queue);这个函数可以设置dispatch object的目标queue,dispatch object 可以使dispatch queue、dispatch source 、Dispatch I/O channels。目标queue就是dispatch object的finalizer(dispatch source)调用者,修改dispatch object的target queue会修改dispatch object如下的行为:
当被修改的dispatch object 为 Dispatch queue时,被修改的queue的优先级与target queue的优先级一致。如果修改一个serial queue 的 target queue为另一个serial queue 。为了下面方便描述,我们把被修改的queue叫做queueA,把target queue叫做queueB。那么在set target queue之后,先把一个blockA异步的提交到queueA,再把blockB异步的提交到queueB,这时候blockB会先于blockA执行。文字描述可能有点不清晰,我们直接来看代码
dispatch_queue_t serial1 = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serial2 = dispatch_queue_create("serial2", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial1, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"blockA");
});
dispatch_async(serial2, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"blockA");
});
dispatch_set_target_queue(serial1, serial2);
dispatch_async(serial1, ^{
NSLog(@"setted target : blockA ");
});
dispatch_async(serial2, ^{
NSLog(@"setted target : blockB");
}); 输出:
2017-01-06 21:02:32.268 GCDTest[7379:1744101] blockA
2017-01-06 21:02:32.268 GCDTest[7379:1744094] blockA
2017-01-06 21:02:32.269 GCDTest[7379:1744094] setted target : blockB
2017-01-06 21:02:32.269 GCDTest[7379:1744094] setted target : blockA
至于为什么是这个结果我现在还不清楚,我猜测应该是提交到queueA的block,等待它的target queue -> queueB的正在排队的办block都提交后,才把blockA提交到target block -> queueB。
当被修改的dispatch object 为 Dispatch source时,由新设置的target queue 来响应 Dispatch source的各个handle。
4.void dispatch_main( void) 这个函数的作用是暂停一下主线程,等待所有被提交到main queue的任务执行完成。一般的通过 UIApplicationMain (iOS), NSApplicationMain (OS X), or CFRunLoopRun 创建的程序是禁止调用这个函数的,所以这个函数我们了解一下即可。
看代码:
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main queue 1");
});
dispatch_main(); // 如果不加这句代码的话,上面提交的block是不会执行的 return ;
}
5.上面提到dispatch_after函数,它的第一个参数需要一个dispatch_time_t类型,由dispatch_time、dispatch_walltime这两个函数生成(也可以取DISPATCH_TIME_NOW/DISPATCH_TIME_FOREVER这两个宏,第一个指现在马上,第二个指永远不)。现在我们来学习一下这两个函数
首先是dispatch_time_t dispatch_time( dispatch_time_t when, int64_t delta); dispatch_time函数需要输入两个参数
when 参数是指定一个开始值。一般取DISPATCH_TIME_NOW,意思是说从现在开始吧。
delta 参数的单位是纳秒,
dispatch_time 函数的作用就是返回一个相距 when 时间的 delta 个纳秒的时间间隔。比如一个延迟5秒的定时任务可以这样写:
// 从现在开始,5s后开始提交block到mian queue
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * );
dispatch_after(when, dispatch_get_main_queue(), ^{
NSLog(@"do something.");
});
dispatch_walltime函数同dispatch_time函数的功能一样的,只是两者的第一个参数类型不一样 ,前者参考的时间是 wall clock,后者参考时间四default clock。至于default clock 与 wall clock 的区别,可以自行了解。如果可以用一个不那么严谨的解释来描述的话,就如先描述:dispatch_time返回的是相距一个时间间隔(此时此刻)多少纳秒的时间间隔,dispatch_walltime返回的是一个相距特定日期时间(2017-01-08 22:30)多少纳秒的时间间隔。
下面给出使用dispatch_walltime的一个例子:
NSDate * date = [NSDate date];
NSLog(@"date:%@",date); // 2017-01-08 22:30:00
NSTimeInterval interval = date.timeIntervalSince1970;
struct timespec time;
time.tv_nsec = NSEC_PER_SEC * interval;
time.tv_sec = ;
dispatch_time_t when = dispatch_walltime(&time,NSEC_PER_SEC*);
// when 等于 从2017-01-8 22:30:00开始5s钟后,(2017-01-08 22:30:05 )
dispatch_after(when, dispatch_get_main_queue(), ^{
NSLog(@"do something.");
});
三、dispatch Groups - 组
通过dispatch groups你可以同步一组blocks,你不需要管blocks执行顺序,在所有被提交到groups的blocks都执行完成后,你会得到通知。整个dispatch_groups 技术涉及3个概念,我们先理一下:
.block:执行任务的代码块,这个不用多说大家都知道
.queue:实际运行block代码的线程,上一节已经谈过了
.qroup:指把一些blocks,人工的组合在一起让它们成为一个组。
一句话总结dispatch groups技术:
创建一个group,并把一些blocks(需要指定执行queue)添加进来,等到所有的blocks都执行完后发通知到group。
我们直接来看代码:
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, serial, ^{
NSLog(@"serial block");
});
dispatch_group_async(group, concurrent, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"concurrent blockA");
});
dispatch_group_async(group, concurrent, ^{
NSLog(@"concurrent blockB");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"group complete");
});
结果输出:
-- ::56.137 GCDTest[:] serial block
-- ::56.137 GCDTest[:] concurrent blockB
-- ::57.142 GCDTest[:] concurrent blockA
-- ::57.142 GCDTest[:] group complete
上面例子中有两个api
void dispatch_group_async( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); 异步的把一个block关联到一个group
void dispatch_group_notify( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);把一个complete block提交给group,当group的所有关联blocks都执行完成后,会执行你提交的complete block。如果group中没有关联的block的,会立即执行complete block。
还有一个同步等待group执行完成通知的API : long dispatch_group_wait( dispatch_group_t group, dispatch_time_t timeout);
dispatch_group_wait会一直阻塞着,直到group所有关联的blocks都执行完成或者第二个参数提供的超时时间已经超时。
我们还可以通过这两个下面api来手动管理group的通知
dispatch_group_enter( dispatch_group_t group); // count 自增 1 (初始化为0)
dispatch_group_leave( dispatch_group_t group); // count 自减 1 (当count 为0时发complete 通知)
我们直接看代码
dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrent2 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(concurrent1, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"concurrent1 block ");
dispatch_async(concurrent2, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"concurrent2 block ");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"group complete");
}); // 输出:
// 2017-01-09 10:26:31.752 GCDTest[10009:1858381] concurrent1 block
// 2017-01-09 10:26:32.756 GCDTest[10009:1858381] concurrent2 block
// 2017-01-09 10:26:32.756 GCDTest[10009:1858042] group complete
四、dispatch Semaphores - 信号量
这个比较简单了,就是一个生产者消费者模式。
首先创建一个信号信号量并指定一个初始化值:
dispatch_semaphore_t dispatch_semaphore_create( long value);
当生产出一个产品时,我们增加一下信号量,下面这个函数返回值是当期有多少个可用的产品
long dispatch_semaphore_signal( dispatch_semaphore_t dsema);
当我们需要产品的时候,调用一下wait函数,如果返回0说明有产品可用,非0则相反
long dispatch_semaphore_wait( dispatch_semaphore_t dsema, dispatch_time_t timeout);
直接上代码:
// 生产者
dispatch_queue_t producer = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT);
// 消费者
dispatch_queue_t client = dispatch_queue_create("client", DISPATCH_QUEUE_CONCURRENT);
// 信号量
dispatch_semaphore_t carFactory = dispatch_semaphore_create();
dispatch_async(producer, ^{
for (int i=; i < ; i++) {
[NSThread sleepForTimeInterval:i];
NSLog(@"[%d]output a car",i+);
dispatch_semaphore_signal(carFactory);
}
});
dispatch_async(client, ^{
while () {
long ret = dispatch_semaphore_wait(carFactory, DISPATCH_TIME_NOW); if (ret == ) {
NSLog(@"get a car");
}
}
});
/*
输出
2017-01-09 11:30:54.754 GCDTest[17921:1948479] [1]output a car
2017-01-09 11:30:54.755 GCDTest[17921:1948486] get a car
2017-01-09 11:30:55.759 GCDTest[17921:1948479] [2]output a car
2017-01-09 11:30:55.760 GCDTest[17921:1948486] get a car
2017-01-09 11:30:57.764 GCDTest[17921:1948479] [3]output a car
2017-01-09 11:30:57.765 GCDTest[17921:1948486] get a car
2017-01-09 11:31:00.767 GCDTest[17921:1948479] [4]output a car
2017-01-09 11:31:00.767 GCDTest[17921:1948486] get a car
2017-01-09 11:31:04.770 GCDTest[17921:1948479] [5]output a car
2017-01-09 11:31:04.770 GCDTest[17921:1948486] get a car
*/
五、dispatch Barriers - 障碍
通过 dispatch_barrier 允许在并行queue中提交一个barrier block,假设我们把提交动作发生的这个时间点记做A点。完成提交barrier block动作后,会一直等待A点前提交的blocks都执行完毕才去执行barrier block,当barrier block被执行完毕后就开始按照concurrent queue既有的规制运行A点后提交的blocks。有两个提交barrier block的API,一个异步的,一个同步的。直接看代码
dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent, ^{
NSLog(@"block A");
});
dispatch_async(concurrent, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"block B");
});
// 异步提交
dispatch_barrier_async(concurrent, ^{
NSLog(@"barrier block ");
});
// 同步提交
dispatch_barrier_sync(concurrent, ^{
NSLog(@"barrier block");
});
dispatch_async(concurrent, ^{
NSLog(@"block C");
});
// 结果
-- ::17.203 GCDTest[:] block A
-- ::19.206 GCDTest[:] block B
-- ::19.207 GCDTest[:] barrier block
-- ::19.208 GCDTest[:] block C
值得注意的是,只能把barrier block提交到自己通过dispatch_queue_crate创建的concurrent queue,如果提交到serial queue、global queue的话,barrier block不会起作用,与dispatch_sync、dispatch_async 无异
六、dispatch sources - 系统源
通过dispatch source 可以监控系统的一些事件,当这些事件发生时会把你的block提交到queue执行。包括下列事件
定时器事件
描述符(文件)事件
信号量事件
进程事件
在这里我只谈谈定时器怎么用的,其他事件在ios doc都有sample code;
dispatch source 有一套api可以用,我先来个概念快照
. dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue) 创建一个dispatch source
. void dispatch_source_cancel( dispatch_source_t source); 取消一个dispatch source
. void dispatch_source_set_event_handler( dispatch_source_t source, dispatch_block_t handler); 设置event handle block
. void dispatch_source_set_cancel_handler( dispatch_source_t source, dispatch_block_t cancel_handler); 设置dispatch source 被取消的 handle block
. void dispatch_source_set_timer( dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway); // 给dispatch source设置一个定时器。
看过api快照后,你应该大概知道怎么用GCD创建一个定时器了 :先创建一个dispatch source ,然后给它设置 event、cancel handler 的block 和 设置timer属性,最后调用dispatch_resume执行dispatch source就行了。我们在代码里面详细说明
__block int count = ;
/*
* dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)
* 第一个参数指明这是一个timer,当然还可以指定其他类型
* 第二个参数是一个系统资源的句柄,比如文件句柄
* 第三个参数为flag
* 第四个参数是handle block被提交到的queue
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , dispatch_get_main_queue());
if (timer) {
// 设置 timer
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, * NSEC_PER_SEC, );
// 设置 event handler
dispatch_source_set_event_handler(timer, ^{
NSLog(@"timer fire");
if (count++ == ) {
// 取消dispatch source
dispatch_source_cancel(timer);
}
});
// 设置 event handler
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"time cancel.");
});
// 启动 dispatch source
// 因为create后还需要配置一些行为,所以需要手动resume dispatch source
dispatch_resume(timer);
// 注意:
// 这里需要保存一下timer到类变量,不然timer是局部变量运行到函数尾部是,这个定时器也就没了
self.timer = timer;
}
七、dispatch I/O - I/O
八、总结
最后再来看看dispatch_object的管理:
//dispatch_object 对象的内存管理,用ARC技术的话,这两个函数就没有用了
void dispatch_release( dispatch_object_t object);
void dispatch_retain( dispatch_object_t object);
// 注意
void dispatch_resume( dispatch_object_t object); // counting 减1 等于0就回复
void dispatch_suspend( dispatch_object_t object); // counting 加1 大于0就挂起 // 可以给dispatch_object设置一个context用于在各个任务之间共享
void * dispatch_get_context( dispatch_object_t object); // 获取 context
void dispatch_set_context( dispatch_object_t object, void *context); // 设置 context // 可以给dispatch_object设置一个执行完成的回调函数
void dispatch_set_finalizer_f( dispatch_object_t object, dispatch_function_t finalizer);
GCD 开发详情的更多相关文章
- 玩转iOS开发:iOS中的GCD开发(三)
上一章, 我们了解到了GCD里的一些队列和任务的知识, 也实践了一下, 同时我们也对主队列的一些小情况了解了一下, 比如上一章讲到的卡线程的问题, 如果没有看的朋友可以去看看玩转iOS开发:iOS中的 ...
- iOS 定时器开发详情
目录 概述 NSTimer performSelector GCD timer CADisplayLink 一.概述 在平时的开发任务中,定时器是我们常用的技术.这一节我们来学习iOS怎么使用定时器. ...
- GCD 开发
一.简介 GCD 的全称是 Grand Centre Dispatch 是一个强大的任务编程管理工具.通过GCD你可以同步或者异步地执行block.function. 二.dispatch Queue ...
- objective-c runtime 开发详情
目录 概述 对象与类的实质 id与class 继承关系与isa 总结 C函数创建一个OC类 OC类与runtime NSObjectProtocol NSObject NSProxy 一.概述 Obj ...
- KVO 开发详情
目录 概念 应用KVO的3个步骤 关联属性的KVO 手动管理KVO通知 一.概念 KVO全称是 Key-Value Observing ,是OC的一种消息发送机制.这个机制是指:假设将B对象注册为A对 ...
- KVC 开发详情
目录 概述 KVC基本技术 KVC访问函数 KVC搜索顺序 KVC集合操作 一.概述 kvc全名是Key-value coding,kvc是一种通过字符串间接的访问oc对象的属性的一种技术. 一个oc ...
- swift开发多线程篇 - 多线程基础
swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread 使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...
- iOS开发——多线程OC篇&多线程详解
多线程详解 前面介绍了多线程的各种方式及其使用,这里补一点关于多线程的概念及相关技巧与使用,相信前面不懂的地方看了这里之后你就对多线程基本上没有什么问题了! 1——首先ios开发多线程中必须了解的概念 ...
- 还在用GCD?来看看NSOperation吧
在iOS开发中,谈到多线程,大家第一时间想到的一定是GCD.GCD固然是一套强大的多线程解决方案,能够解决绝大多数的多线程问题,但是他易于上手难于精通且到处是坑的特点也注定了想熟练使用它有一定的难度. ...
随机推荐
- wgan pytorch,pyvision, py-faster-rcnn等的安装使用
因为最近在读gan的相关工作,wgan的工作不得不赞.于是直接去跑了一下wgan的代码. 原作者的wgan是在lsun上测试的,而且是基于pytorch和pyvision的,于是要装,但是由于我们一直 ...
- 史上最简单的SpringCloud教程 | 第八篇: 消息总线(Spring Cloud Bus)
转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springcloud/2017/07/12/sc08-bus/ 本文出自方志朋的博客 最新Finchley版本请 ...
- iOS面试题总结(持续更新)
过段时间打算跳槽,找了一些面试题来做,在这里做个总结方便review,希望能对要面试的童鞋有帮助. 以下为面试题: 运行以下代码会有什么结果 NSString *str1 = @"str1& ...
- 使用Git操作码云
一.安装并配置 .安装git 下载地址: 官方网站:https://git-for-windows.github.io/ 国内镜像:https://pan.baidu.com/s/1kU5OCOB#l ...
- 【赛时总结】◇赛时·V◇ Codeforces Round #486 Div3
◇赛时·V◇ Codeforces Round #486 Div3 又是一场历史悠久的比赛,老师拉着我回来考古了……为了不抢了后面一些同学的排名,我没有做A题 ◆ 题目&解析 [B题]Subs ...
- spring-mybatis整合项目 异常处理
java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorIm ...
- MongoDB模糊查询
模糊查询简介MongoDB查询条件可以使用正则表达式,从而实现模糊查询的功能.模糊查询可以使用$regex操作符或直接使用正则表达式对象. MySQL MongoDB select * from s ...
- ethereum(以太坊)(三)--合约单继承与多继承
pragma solidity ^0.4.0; // priveta public internal contract Test{ //defualt internal uint8 internal ...
- python之doctest的用法
doctest是python自带的一个模块,你可以把它叫做“文档测试”(doctest)模块. doctest的使用有两种方式:一个是嵌入到python源中.另一个是放到一个独立文件. doctest ...
- 操作 Java 数组的 12 个最佳方法
1. 声明一个数组 Java代码: String[] aArray = new String[5]; String[] bArray = {"a","b",& ...