Grand Central Dispatch(GCD)与performSelector – 需要更好的解释

我已经在我的应用程序中使用了GCD和performSelectorOnMainThread:waitUntilDone,并且倾向于认为它们是可互换的 – 也就是说,performSelectorOnMainThread:waitUntilDone是GCD C语法的Obj-C包装器。 我一直在想这两个命令是等价的:

dispatch_sync(dispatch_get_main_queue(), ^{ [self doit:YES]; }); [self performSelectorOnMainThread:@selector(doit:) withObject:YES waitUntilDone:YES]; 

我错了吗? 也就是说,performSelector *指令和GCD指令是否有区别呢? 我已经阅读了很多关于它们的文档,但是还没有看到明确的答案。

performSelectorOnMainThread:不使用GCD将消息发送给主线程上的对象。

以下是文档说明该方法的实现方式:

 - (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait { [[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes]; } 

performSelector:target:withObject:order:modes: ,文档指出:

此方法设置一个计时器,以在下次运行循环迭代开始时在当前线程的运行循环中执行aSelector消息。 定时器被configuration为以modes参数指定的模式运行。 当定时器触发时,线程将尝试从运行循环中出列消息并执行select器。 如果运行循环正在运行并处于指定模式之一,则成功; 否则,定时器将等待,直到运行循环处于这些模式之一。

正如雅各所指出的那样,虽然它们看起来是一样的,但却是不同的东西。 实际上,如果您已经在主线程上运行,那么它们处理向主线程发送操作的方式会有很大差异。

我最近遇到了这个问题,我有一个常用的方法,有时是从主线程上运行,有时不是。 为了保护某些UI更新,我一直使用-performSelectorOnMainThread:对于他们没有任何问题。

当我切换到在主队列上使用dispatch_sync时,只要在主队列上运行此方法,应用程序就会死锁。 阅读dispatch_sync的文档,我们看到:

调用此函数并将当前队列定位将导致死锁。

在哪里for -performSelectorOnMainThread:我们看到

等待

一个布尔值,指定当前线程是否在主线程上的接收器上执行指定的select器之后阻塞。 指定YES阻止此线程; 否则,指定NO使该方法立即返回。

如果当前线程也是主线程,并且您为此参数指定了YES,则会立即传送并处理该消息。

我仍然更喜欢GCD的优雅,它提供了更好的编译时检查,以及它在参数等方面的更大的灵活性,所以我做了这个小辅助函数来防止死锁:

 void runOnMainQueueWithoutDeadlocking(void (^block)(void)) { if ([NSThread isMainThread]) { block(); } else { dispatch_sync(dispatch_get_main_queue(), block); } } 

更新:为了响应Dave Dribin指出dispatch_get_current_queue()的警告部分 ,我已经更改为在上面的代码中使用[NSThread isMainThread]

我然后使用

 runOnMainQueueWithoutDeadlocking(^{ //Do stuff }); 

执行我需要在主线程上保证的动作,而不必担心原始方法执行的线程。

GCD的方式是假设更高效和更容易处理,并且仅在iOS4以上版本中可用,而在较旧和较新的iOS中支持performSelector。