转自:http://blog.csdn.net/hmt20130412/article/details/24132171
1.基本介绍:
(1) Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。在Mac OS X 10.6雪豹中首次推出,并在最近引入到了iOS4.0。
(2) GCD是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。(3) 它是IOS多线程抽象层次最高的一层,下面还有前面2章节介绍的更加轻量级的Cocoa operations,和Thread。
2.主要方法:
(1)创建一个队列
dispatch_queue_t queue = dispatch_queue_create("LoadImage", NULL);
其中,第一个参数是标识队列的,第二个参数是用来定义队列的参数(目前不支持,因此传入NULL)。
(2)执行一个队列(async--->异步,sync--->同步)
dispatch_async(queue, ^{ [self doSomething]; });
其中,首先传入之前创建的队列,然后提供由队列运行的代码块。
(3)声明并执行一个队列 (如果不需要保留要运行的队列的引用)
dispatch_async(dispatch_queue_create ("LoadImage", NULL), ^{ [self doSomething]; });
(4)暂停一个队列
dispatch_suspend(queue);
(5)恢复一个队列(如果暂停一个队列不要忘记恢复)
dispatch_resume(queue);
(6)将代码块中的工作转回到主线程(注意,dispatch_suspend (以及dispatch_resume)在主线程上不起作用)
dispatch_sync(dispatch_get_main_queue(), ^{ [self dismissLoginWindow]; });
3.代码示例
- #pragma mark - GCD(Grand Central Dispatch)
- - (void)GCD{
- dispatch_queue_t queue = dispatch_queue_create("test", NULL);
- // 多一个a,异步
- dispatch_async(queue, ^{
- for (int i = 0; i < 100; i++) {
- NSLog(@"--多线程--%d",i);
- }
- // 判断是否是在多线程运行环境
- BOOL isMulti = [NSThread isMultiThreaded];
- NSLog(@"%d",isMulti);
- if (isMulti) {
- NSLog(@"*********多线程***********");
- }
- // 将代码块中的工作转回到主线程
- dispatch_sync(dispatch_get_main_queue(), ^{
- // 判断是否是在主线程运行环境
- BOOL isMain = [NSThread isMainThread];
- if (isMain) {
- NSLog(@"*********主线程**********");
- }
- });
- });
- /* // 通过此方式,还是运行在当前线程
- dispatch_sync(queue, ^{
- // 主线程
- });
- */
- for (int i = 0; i < 100; i++) {
- NSLog(@"--主线程--%d",i);
- }
- }
4.加载网络图片(在多线程加载明显比放在主线程加载快N多)
- #pragma mark - 给UIImageView写一个类目
- @interface UIImageView (WebCach)
- - (void)setImageWithURL:(NSURL *)url;
- @end
- #import "UIImageView+WebCach.h"
- @implementation UIImageView (WebCach)
- - (void)setImageWithURL:(NSURL *)url{
- dispatch_queue_t queue = dispatch_queue_create("loadImage", NULL);
- dispatch_async(queue, ^{
- NSData * data = [NSData dataWithContentsOfURL:url];
- UIImage * image = [UIImage imageWithData:data];
- // 加载UI的操作,一般放在主线程进行
- dispatch_async(dispatch_get_main_queue(), ^{
- self.image = image;
- });
- });
- }
- @end
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- // Do any additional setup after loading the view.
- UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
- [imageView setImageWithURL:[NSURL URLWithString:@"http://www.baidu.com/img/bdlogo.gif"]];
- [self.view addSubview:imageView];
- }
5.最后,引用别人博客对几个方法的介绍
@dispatch对象
- 原文地址 http://www.cnblogs.com/sunfrog/p/3243230.html
- 谈起iOS的dispatch(正式称谓是Grand Central Dispatch或GCD),不得不说这又是iOS(包括MacOSX)平台的创新,优缺点这里不讨论,只有当你使用时才能真正体会到。我们说dispatch函数的主要目的是实现多任务并发代码,那么要理解dispatch函数,先来了解dispatch对象的定义。
- dispatch对象类型的部分定义,主要使用C语言的宏定义:
- <os/object.h>文件:
- #define OS_OBJECT_CLASS(name) OS_##name
- #define OS_OBJECT_DECL(name, ...) \
- @protocol OS_OBJECT_CLASS(name) __VA_ARGS__ \
- @end \
- typedef NSObject<OS_OBJECT_CLASS(name)> *name##_t
- #define OS_OBJECT_DECL_SUBCLASS(name, super) \
- OS_OBJECT_DECL(name, <OS_OBJECT_CLASS(super)>)
- <dispatch/object.h>文件:
- #define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
- #define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
- OS_OBJECT_DECL(dispatch_object); //定义dispatch_object_t
- <dispatch/queue.h>文件(dispatch队列类定义,其它dispatch对象类似):
- DISPATCH_DECL(dispatch_queue); //定义dispatch_queue_t
- 可以通过Xcode预编译后可以看到最终结果,最终定义的都是NSObject类,虽然它们之间没用直接继承关系,但都实现OS_dispatch_object接口,这样dispatch_queue_t对象也同样是dispatch_object_t的对象了。下面就是预编译dispatch_object_t和dispatch_queue_t的结果:
- @protocol OS_dispatch_object
- @end
- typedef NSObject<OS_dispatch_object> *dispatch_object_t;
- @protocol OS_dispatch_queue <OS_dispatch_object>
- @end
- typedef NSObject<OS_dispatch_queue> *dispatch_queue_t;
- 由于dispatch api接口定义成C函数的形式,dispatch的对象都是由C函数形式的厂方法得到(不能继承dispatch类,不用alloc),这样做隐藏dispatch对象的具体形态,把注意力放在如何调用dispatch api上。
- 从上面dispatch对象宏定义可以看到dispatch对象类的名称一般为dispatch_xyz_t(严格来讲是对象指针),它们都可以看成dispatch_object_t的子类(对象指针),所以使用dispatch对象时套用这个概念就行。
- 有关dispatch对象的基本接口如下:
- void dispatch_retain(dispatch_object_t object); //替代dispatch对象常规的retain来持有对象,但ARC编程中不再允许
- void dispatch_release(dispatch_object_t object); //替代dispatch对象常规的release来释放对象,同样ARC编程中不再允许
- void dispatch_set_context(dispatch_object_t object, voidvoid *context); //给dispatch对象绑定特定数据对象(类似线程的TLS数据),会被传给dispatch对象的finalizer函数
- voidvoid *dispatch_get_context(dispatch_object_t object); //返回dispatch对象绑定的数据对象指针
- void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer); //设置dispatch对象的finalizer函数,当该对象释放时会调用finalizer,部分代码解释如何使用这个函数(ARC模式):
- dispatch_object_t dispatchObject = ...;
- voidvoid *context = ...;
- dispatch_set_context(dispatchObject, context);
- dispatch_set_finalizer_f(dispatchObject, finalizer);
- ......
- dispatchObject = nil; //dispatchObject被释放,这时调用finalizer函数
- ......
- void finalizer(voidvoid *context)
- {
- //处理或释放context相关资源
- }
- dispatch对象的另外两个接口是:
- void dispatch_resume(dispatch_object_t object); //激活(启动)在dispatch对象上的block调用,可以运行多个block
- void dispatch_suspend(dispatch_object_t object); //挂起(暂停)在dispatch对象上的block调用,已经运行的block不会停止
- 一般这两个函数的调用必须成对,否则运行会出现异常。
- 至此你是否发现这两个函数有些与众不同呢?好像从来没有这么使用对象的,启动对象--暂停对象,呵呵。这正是理解dispatch对象的关键所在。dispatch对象其实是抽象的任务,把动态的任务变成对象来管理。任务是动态的,不存在继承关系,这就是为什么GCD没有提供静态继承dispatch对象类的方式。如果能这样理解,那么在使用dispatch函数时就能够更灵活地去编写代码,实现各种并发的多任务代码。
@dispatch队列
- GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致。
- dispatch队列的生成可以有这几种方式:
- 1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。
- 2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行
- 3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。
- 官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。
- 4. dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。
- 接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。
- dispatch_async(queue, ^{
- //block具体代码
- }); //异步执行block,函数立即返回
- dispatch_sync(queue, ^{
- //block具体代码
- }); //同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。
- 实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。
- 如果queue1是一个串行队列的话,这段代码立即产生死锁:
- dispatch_sync(queue1, ^{
- dispatch_sync(queue1, ^{
- ......
- });
- ......
- });
- 不妨思考下,为什么下面代码也肯定死锁:
- dispatch_sync(dispatch_get_main_queue(), ^{
- ......
- });
- 那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- //子线程中开始网络请求数据
- //更新数据模型
- dispatch_sync(dispatch_get_main_queue(), ^{
- //在主线程中更新UI代码
- });
- });
- 程序的后台运行和UI更新代码紧凑,代码逻辑一目了然。
- dispatch队列是线程安全的,可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:
- dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);
- - (void)writeDB:(NSData *)data
- {
- dispatch_async(queue1, ^{
- //write database
- });
- }
- 下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。
- dispatch队列还实现其它一些常用函数,包括:
- void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
- void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
- void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数
- void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延迟执行block
- 最后再来看看dispatch队列的一个很有特色的函数:
- void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
- 它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源(以后博文会介绍)。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
- dispatch_set_target_queue(dispatchA, dispatchB);
- 那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:
- dispatch_suspend(dispatchA);
- 则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。
- 这里只简单举个例子,说明dispatch队列运行的灵活性,在实际应用中你会逐步发掘出它的潜力。
- dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。
@dispatch源
- 原文地址 http://www.cnblogs.com/sunfrog/p/3243230.html
- dispatch源(dispatch source)和RunLoop源概念上有些类似的地方,而且使用起来更简单。要很好地理解dispatch源,其实把它看成一种特别的生产消费模式。dispatch源好比生产的数据,当有新数据时,会自动在dispatch指定的队列(即消费队列)上运行相应地block,生产和消费同步是dispatch源会自动管理的。
- dispatch源的使用基本为以下步骤:
- 1. dispatch_source_t source = dispatch_source_create(dispatch_source_type, handler, mask, dispatch_queue); //创建dispatch源,这里使用加法来合并dispatch源数据,最后一个参数是指定dispatch队列
- 2. dispatch_source_set_event_handler(source, ^{ //设置响应dispatch源事件的block,在dispatch源指定的队列上运行
- //可以通过dispatch_source_get_data(source)来得到dispatch源数据
- });
- 3. dispatch_resume(source); //dispatch源创建后处于suspend状态,所以需要启动dispatch源
- 4. dispatch_source_merge_data(source, value); //合并dispatch源数据,在dispatch源的block中,dispatch_source_get_data(source)就会得到value。
- 是不是很简单?而且完全不必编写同步的代码。比如网络请求数据的模式,就可以这样来写:
- dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0));
- dispatch_source_set_event_handler(source, ^{
- dispatch_sync(dispatch_get_main_queue(), ^{
- //更新UI
- });
- });
- dispatch_resume(source);
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- //网络请求
- dispatch_source_merge_data(source, 1); //通知队列
- });
- dispatch源还支持其它一些系统源,包括定时器、监控文件的读写、监控文件系统、监控信号或进程等,基本上调用的方式原理和上面相同,只是有可能是系统自动触发事件。比如dispatch定时器:
- dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
- dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 10*NSEC_PER_SEC, 1*NSEC_PER_SEC); //每10秒触发timer,误差1秒
- dispatch_source_set_event_handler(timer, ^{
- //定时处理
- });
- dispatch_resume(timer);
- 其它情况的dispatch源就不再一一举例,可参看官网有具体文档: https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW1
- 最后,dispatch源的其它一些函数大致罗列如下:
- uintptr_t dispatch_source_get_handle(dispatch_source_t source); //得到dispatch源创建,即调用dispatch_source_create的第二个参数
- unsignedlong dispatch_source_get_mask(dispatch_source_t source); //得到dispatch源创建,即调用dispatch_source_create的第三个参数
- void dispatch_source_cancel(dispatch_source_t source); //取消dispatch源的事件处理--即不再调用block。如果调用dispatch_suspend只是暂停dispatch源。
- long dispatch_source_testcancel(dispatch_source_t source); //检测是否dispatch源被取消,如果返回非0值则表明dispatch源已经被取消
- void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); //dispatch源取消时调用的block,一般用于关闭文件或socket等,释放相关资源
- void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); //可用于设置dispatch源启动时调用block,调用完成后即释放这个block。也可在dispatch源运行当中随时调用这个函数。
@dispatch同步
- 原文地址 http://www.cnblogs.com/sunfrog/p/3243230.html
- GCD提供两种方式支持dispatch队列同步,即dispatch组和信号量。
- 一、dispatch组(dispatch group)
- 1. 创建dispatch组
- dispatch_group_t group = dispatch_group_create();
- 2. 启动dispatch队列中的block关联到group中
- dispatch_group_async(group, queue, ^{
- // 。。。
- });
- 3. 等待group关联的block执行完毕,也可以设置超时参数
- dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
- 4. 为group设置通知一个block,当group关联的block执行完毕后,就调用这个block。类似dispatch_barrier_async。
- dispatch_group_notify(group, queue, ^{
- // 。。。
- });
- 5. 手动管理group关联的block的运行状态(或计数),进入和退出group次数必须匹配
- dispatch_group_enter(group);
- dispatch_group_leave(group);
- 所以下面的两种调用其实是等价的,
- A)
- dispatch_group_async(group, queue, ^{
- // 。。。
- });
- B)
- dispatch_group_enter(group);
- dispatch_async(queue, ^{
- //。。。
- dispatch_group_leave(group);
- });
- 所以,可以利用dispatch_group_enter、 dispatch_group_leave和dispatch_group_wait来实现同步,具体例子:http://stackoverflow.com/questions/10643797/wait-until-multiple-operations-executed-including-completion-block-afnetworki/10644282#10644282。
- 二、dispatch信号量(dispatch semaphore)
- 1. 创建信号量,可以设置信号量的资源数。0表示没有资源,调用dispatch_semaphore_wait会立即等待。
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- 2. 等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- 3. 通知信号,如果等待线程被唤醒则返回非0,否则返回0。
- dispatch_semaphore_signal(semaphore);
- 最后,还是回到生成消费者的例子,使用dispatch信号量是如何实现同步:
- dispatch_semaphore_t sem = dispatch_semaphore_create(0);
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //消费者队列
- while (condition) {
- if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 10*NSEC_PER_SEC))) //等待10秒
- continue;
- //得到数据
- }
- });
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //生产者队列
- while (condition) {
- if (!dispatch_semaphore_signal(sem))
- {
- sleep(1); //wait for a while
- continue;
- }
- //通知成功
- }
- });