我们刚开始学习iOS的时候,被告知UI操作必须在主线程中进行。这是因为UIKit的方法不是线程安全的,保证线程安全需要巨大的开销。那么问题来了,在主线程中进行UI操作安全吗?显然,答案是否定的!在Apple的MapKit框架中,有一个方法叫做addOverlay。在底层实现的时候,不仅需要代码在主线程上执行,还需要在GCD的主队列上执行。这是一个极其罕见的问题,但是有人在使用ReactiveCocoa的时候踩过这个坑,提交了一个issue。Apple的开发人员技术支持承认这是一个错误。不管这是bug还是历史遗留的设计,是不是死胡同,为了避免再次掉进同一个坑,我觉得还是有必要分析一下问题的原因和解决办法.GCD知识复习在GCD中,使用dispatch_get_main_queue()函数获取主队列。调用dispatch_sync()方法会将任务同步提交到指定队列。注意队列和线程的区别。他们之间没有“所有权”。当我们同步提交一个任务时,我们会先阻塞当前队列,然后等待下一个runloop在合适的线程中执行这个阻塞。.在执行block之前,它会先找到一个合适的线程来执行block,然后阻塞这个线程,直到block执行完毕。寻找线程的规则是:任何提交到主队列的块都会在主线程中执行。在不违反这个规则的前提下,文档中还告诉我们,系统会自动优化并尽可能在当前线程中执行块。顺便说一句,GCD死锁的充分条件是:“重复同步提交块到当前队列”。从原理上看,死锁的原因是提交的块阻塞了队列,队列阻塞后dispatch_sync()永远无法执行。可见这与代码所在线程无关。另一个例子也可以证明这一点。将块同步分派到主线程中的串行队列。根据上面的线程选择原则,block会在主线程中执行,但不会造成死锁:dispatch_queue_tqueue=dispatch_queue_create("com.kt.deadlock",nil);dispatch_sync(queue,^{NSLog(@"currentthread=%@",[NSThreadcurrentThread]);});//输出结果://currentthread={number=1,name=main}原因分析一直这么啰嗦,让我们回到前面描述的错误。现在我们知道即使在主线程中执行的代码也很可能不在主队列中运行(反之亦然)。如果我们在子队列中调用MapKit的addOverlay方法,即使我们当前在主线程中,也会导致bug,因为该方法的底层实现判断的是主队列,而不是主线程。进一步思考,有时候为了保证UI操作运行在主线程,如果有一个函数可以用来新建UILabel,为了保证线程安全,代码可能是这样的:-(UILabel*)labelWithText:(NSString*)text{__blockUILabel*theLabel;if([NSThreadisMainThread]){theLabel=[[UILabelalloc]init];[theLabelsetText:text];}else{dispatch_sync(dispatch_get_main_queue(),^{theLabel=[[UILabelalloc]init];[theLabelsetText:text];});}returntheLabel;}这种写法严格来说并不是100%安全的,因为我们无法知道相关系统方法是否存在上述bug。解决方案由于提交到主队列的block必须运行在主线程上,而GCD中的线程切换通常是指定某个队列引起的,所以我们可以做一个更严格的判断,即判断是否在主队列中是不是在主线程。GCD没有提供API来做相应的判断,但是我们可以通过dispatch_queue_set_specific和dispatch_get_specific方法找到另一种方法来标记主队列:+(BOOL)isMainQueue{staticconstvoid*mainQueueKey=@"mainQueue";staticvoid*mainQueueContext=@"mainQueue";staticdispatch_once_tonceToken;dispatch_once(&onceToken,^{dispatch_queue_set_specific(dispatch_get_main_queue(),mainQueueKey,mainQueueContext,nil);});returndispatch_get_specific(mainQueueKey)==mainQueueContext;}使用isMainQueue方法代替in[NSThreadisMain安全。参考1.关于MapKit的社区bug报告http://t.cn/RtxivSc2.GCD的MainQueuevsMainThreadhttp://t.cn/RthOawx3.ReactiveCocoa遇到了类似的坑http://t.cn/RtxJFRX4。为什么我们不能在当前队列上使用dispatch_sync?http://t.cn/RtxJgPi
