48

我在我的应用程序中同时使用了 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 命令有区别吗?我已经阅读了很多关于它们的文档,但还没有看到明确的答案。

4

3 回答 3

70

正如雅各布所指出的,虽然它们可能看起来相同,但它们是不同的东西。事实上,如果您已经在主线程上运行,它们处理向主线程发送操作的方式存在显着差异。

我最近遇到了这个问题,我有一个通用方法,有时是从主线程上的某个东西运行的,有时不是。为了保护某些 UI 更新,我一直在毫无问题地使用-performSelectorOnMainThread:它们。

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

调用此函数并以当前队列为目标会导致死锁。

我们在哪里-performSelectorOnMainThread:看到

等待

一个布尔值,指定当前线程是否阻塞,直到在主线程上的接收器上执行指定的选择器之后。指定 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
});

执行我需要在主线程上保护的操作,而不用担心原始方法在哪个线程上执行。

于 2011-03-07T22:53:46.723 回答
22

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 消息。定时器被配置为在模式参数指定的模式下运行。当计时器触发时,线程尝试将消息从运行循环中取出并执行选择器。如果运行循环正在运行并且处于指定模式之一,则成功;否则,计时器会一直等待,直到运行循环处于其中一种模式。

于 2011-03-07T20:57:05.447 回答
2

GCD 的方式被认为更高效且更易于处理,并且仅在 iOS4 及更高版本中可用,而在较旧和较新的 iOS 中都支持 performSelector。

于 2011-03-07T23:12:54.310 回答