iOS开发之GCD基础
重新回顾、学习GCD、Block。先贴出一篇不错的讲解GCD基础使用的文章
原文地址:http://blog.csdn.net/aolan1108/article/details/17283415
做了2年的ios开发,很想静下心来想想,做一些总结,但是苦于生活和工作方面的种种原因,一直没能如愿。今天终于下定决心,把自己所学所想记录下来,方便以后查看,同时可供大家分享。
-
//主线程异步执行 -(void)action1{ dispatch_async(dispatch_get_main_queue(), ^{ for (int i = 0; i< 5; i++) NSLog(@"主线程异步执行===========%d",i); }); NSLog(@"=================主线程异步执行"); }
2013-12-09 14:36:20.863 TestGCD[872:a0b] =================主线程异步执行
2013-12-09 14:36:20.864 TestGCD[872:a0b] 主线程异步执行===========0
2013-12-09 14:36:20.865 TestGCD[872:a0b] 主线程异步执行===========1
2013-12-09 14:36:20.865 TestGCD[872:a0b] 主线程异步执行===========2
2013-12-09 14:36:20.865 TestGCD[872:a0b] 主线程异步执行===========3
2013-12-09 14:36:20.866 TestGCD[872:a0b] 主线程异步执行===========4
-
//主线程同步执行 -(void)action2{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i< 3; i++) NSLog(@"并发线程异步执行===========%d",i); dispatch_sync(dispatch_get_main_queue(), ^{ for (int i = 0; i< 3; i++) NSLog(@"主线程同步执行===========%d",i); }); NSLog(@"===========主线程执行完毕"); }); NSLog(@"===========并发线程可能正在执行"); }
打印结果:
2013-12-09 15:22:22.352 TestGCD[1269:a0b] ===========并发线程可能正在执行
2013-12-09 15:22:22.352 TestGCD[1269:1403] 并发线程异步执行===========0
2013-12-09 15:22:22.355 TestGCD[1269:1403] 并发线程异步执行===========1
2013-12-09 15:22:22.356 TestGCD[1269:1403] 并发线程异步执行===========2
2013-12-09 15:22:22.357 TestGCD[1269:a0b] 主线程同步执行===========0
2013-12-09 15:22:22.358 TestGCD[1269:a0b] 主线程同步执行===========1
2013-12-09 15:22:22.358 TestGCD[1269:a0b] 主线程同步执行===========2
2013-12-09 15:22:22.359 TestGCD[1269:1403] ===========主线程执行完毕
从结果中我们仔细理解,就会比较深刻,首先我们看到打印语句4最先执行(有可能不是最先执行,这个是由系统决定的),说明我们提交到并发队列dispatch_get_global_queue的执行方式是异步的;其次打印语句2是在打印语句1执行完才开始执行,这说明dispatch_get_global_queue虽然是并发队列,但是其内部的任务执行顺序是串行的;最后,我们看到打印语句3是在打印语句2执行完成后再执行,说明主线程同步执行是阻塞的,我们通常会将UI的刷新用同步方式放到主线程中去操作,当然这种操作的时间一般都比较短,以至于用户几乎无法察觉。
例子三,其实GCD的操作真正的意义当然不在于在主线程中做一些操作,在很多时候,我们都会用到后台线程,如果你的iphone是多核的cpu,那么恭喜你,你可以有流畅的操作体验,即使不是多核,你的操作体验也相对流畅,因为你可以将需要大量时间才能执行完成的任务放到后台线程中去执行,用户就不会有死机感,譬如说图片加载、网络请求、复杂逻辑的数据解析等等。
-(void)action3{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i< 10000; i++) NSLog(@"=================%d",i); }); NSLog(@"=================后台异步执行"); }
你可以迅速点击两次按钮,即连续执行两次action3方法,在这里,我们截取部分打印结果:
2013-12-09 15:51:30.864 TestGCD[1350:3007] =================0
2013-12-09 15:51:30.864 TestGCD[1350:a0b] =================后台异步执行
2013-12-09 15:51:30.864 TestGCD[1350:3007] =================1
2013-12-09 15:51:30.865 TestGCD[1350:3007] =================2
2013-12-09 15:51:30.865 TestGCD[1350:3007] =================3
......
2013-12-09 15:51:31.007 TestGCD[1350:3007] =================600
2013-12-09 15:51:31.007 TestGCD[1350:3007] =================601
2013-12-09 15:51:31.007 TestGCD[1350:a0b] =================后台异步执行
2013-12-09 15:51:31.007 TestGCD[1350:3007] =================602
2013-12-09 15:51:31.008 TestGCD[1350:3007] =================603
2013-12-09 15:51:31.007 TestGCD[1350:473] =================0
2013-12-09 15:51:31.008 TestGCD[1350:473] =================1
2013-12-09 15:51:31.008 TestGCD[1350:473] =================2
2013-12-09 15:51:31.008 TestGCD[1350:3007] =================604
2013-12-09 15:51:31.008 TestGCD[1350:473] =================3
2013-12-09 15:51:31.008 TestGCD[1350:3007] =================605
......
我们看到由于action3被执行了两次,在点击第一次后,屏幕仍然接受点击时间,说明主线程没有被阻塞,用户体验仍然很流畅。两次点击实际上是提交了两个任务到dispatch_get_global_queue队列上,第一次任务并没有执行完成,第二次任务就开始执行,说明dispatch_get_global_queue是并行队列,即任务块与任务块之间是并发执行的。
例子四,肯定会有小伙伴们说后台既然可以异步执行,那么应该也可以同步执行喽,是的,你是对的,但是在实际的开发过程中没有什么意义,我们会发现改方法会产生阻塞,但是却不能用来刷新UI,也没有体现多核处理器的优势。
-
//后台同步执行 -(void)action4{ dispatch_sync(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i< 10000; i++) NSLog(@"=================%d",i); }); NSLog(@"=================后台同步执行"); }
我们也是连续点击两次按钮,执行两次action4方法,我们看到的打印结果让我们很惊讶,可能会有人说,dispatch_get_global_queue队列不是并发队列吗,怎么执行的结果是这样的。在这里我需要解释下,由于提交给全局队列的执行方式是同步的,这里实际上是产生了阻塞,即必须是该任务完成后才能执行下面的任务,我们看到打印语句2执行的位置就知道了。所以同步一般在刷新UI界面或者处理共享数据的时候使用,而且任务的处理时间不能太长,会影响用户体验。
2013-12-09 16:04:35.967 TestGCD[1385:a0b] =================0
2013-12-09 16:04:35.969 TestGCD[1385:a0b] =================1
2013-12-09 16:04:35.969 TestGCD[1385:a0b] =================2
2013-12-09 16:04:40.645 TestGCD[1385:a0b] =================9997
2013-12-09 16:04:40.645 TestGCD[1385:a0b] =================9998
2013-12-09 16:04:40.646 TestGCD[1385:a0b] =================9999
2013-12-09 16:04:40.646 TestGCD[1385:a0b] =================后台同步执行
2013-12-09 16:04:40.647 TestGCD[1385:a0b] =================0
2013-12-09 16:04:40.647 TestGCD[1385:a0b] =================1
2013-12-09 16:04:40.648 TestGCD[1385:a0b] =================2
2013-12-09 16:04:40.648 TestGCD[1385:a0b] =================3
......
2013-12-09 16:04:45.218 TestGCD[1385:a0b] =================9998
2013-12-09 16:04:45.218 TestGCD[1385:a0b] =================9999
2013-12-09 16:04:45.218 TestGCD[1385:a0b] =================后台同步执行
例子五,开发人员可以自己定义一个队列用于执行后台线程,同全局队列使用方法基本类似,在非arc的情况下,需要开发人员手动释放。
-
//自定义dispatch_queue_t -(void)action5{ dispatch_queue_t urls_queue = dispatch_queue_create("myQueue", NULL); dispatch_async(urls_queue, ^{ for (int i = 0; i< 5; i++) NSLog(@"自定义dispatch_queue_t===========%d",i); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"同主线程交互==========="); }); }); dispatch_release(urls_queue); //arc不要 }
打印结果:
2013-12-09 16:43:52.416 TestGCD[1464:1417] 自定义dispatch_queue_t===========0
2013-12-09 16:43:52.417 TestGCD[1464:1417] 自定义dispatch_queue_t===========1
2013-12-09 16:43:52.418 TestGCD[1464:1417] 自定义dispatch_queue_t===========2
2013-12-09 16:43:52.419 TestGCD[1464:1417] 自定义dispatch_queue_t===========3
2013-12-09 16:43:52.420 TestGCD[1464:1417] 自定义dispatch_queue_t===========4
2013-12-09 16:43:52.420 TestGCD[1464:a0b] 同主线程交互===========
例子六,除此之外,调度方式还有延迟执行。
-
//延迟3秒执行 -(void)action6{ double delayInSeconds = 3.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"延迟3秒执行==========="); }); }
打印结果:
2013-12-09 16:48:58.473 TestGCD[1484:a0b] 延迟3秒执行===========
例子七,一次性执行,主要用于创建单例对象。在ios4.0以前,我们创建单例会用到互斥锁@synchronized来确保其他线程没有对self对象进行修改,一般在共用变量的时候使用。
-
+ (NetworkManager *)getNetworkInstance{ @synchronized(self){ if (nil == network) network = [[NetworkManager alloc] init]; } return network; }
ios4.0之后,我们可以用GCD创建单例,下面的GCD语法只会执行一次,代码如下:
-
+(UserManager *)sharedManager{ static UserManager *_manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _manager = [[UserManager alloc]init]; }); return _manager; }
该方法的好处有:一、线程安全;二、很好满足静态分析器的要求;三、兼容ARC;四、仅需要少量代码。
例子八,合并汇总结果,dispatch_group_t队列非常好用,当我们有多个异步执行的队列在执行,我们还有一个任务需要用到这多个异步执行队列执行的结果时,我们就会用到dispatch_group_t,废话不多说,直接上代码:
-
//合并汇总结果 -(void)action8{ dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 5; i++) NSLog(@"并行执行的线程一=============%d",i); }); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 5; i++) NSLog(@"并行执行的线程二=============%d",i); }); dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"汇总结果==========="); }); dispatch_release(group); }
2013-12-10 11:48:39.139 TestGCD[699:1403] 并行执行的线程一=============0
2013-12-10 11:48:39.139 TestGCD[699:3807] 并行执行的线程二=============0
2013-12-10 11:48:39.143 TestGCD[699:1403] 并行执行的线程一=============1
2013-12-10 11:48:39.143 TestGCD[699:3807] 并行执行的线程二=============1
2013-12-10 11:48:39.144 TestGCD[699:1403] 并行执行的线程一=============2
2013-12-10 11:48:39.144 TestGCD[699:3807] 并行执行的线程二=============2
2013-12-10 11:48:39.145 TestGCD[699:1403] 并行执行的线程一=============3
2013-12-10 11:48:39.145 TestGCD[699:3807] 并行执行的线程二=============3
2013-12-10 11:48:39.146 TestGCD[699:3807] 并行执行的线程二=============4
2013-12-10 11:48:39.146 TestGCD[699:1403] 并行执行的线程一=============4
2013-12-10 11:48:39.147 TestGCD[699:3807] 汇总结果===========
我们看到第三条打印语句是在前两条打印语句执行之后才执行。
这也是我综合了官方文档和网上的很多资料总结出来的,并且用实际的代码调试出来的结果,希望能给大家一些帮助,对于其中的不足,欢迎大家给出不同意见。
参考:http://www.cnblogs.com/pure/archive/2013/03/31/2977420.html
2014_07_16今天再次翻看GCD。再浏览过程中,发现了另外一篇不错的讲解队列的帖子,现转发之,希望能对你有所帮助和启发,里面提到死锁的问题,可以思考和交流。
原文地址:http://www.cnblogs.com/sunfrog/p/3305614.html
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,不得不说这是个小小的缺憾。