主队列上的 performSelectorOnMainThread: 和 dispatch_async() 有啥区别?

Posted

技术标签:

【中文标题】主队列上的 performSelectorOnMainThread: 和 dispatch_async() 有啥区别?【英文标题】:What's the difference between performSelectorOnMainThread: and dispatch_async() on main queue?主队列上的 performSelectorOnMainThread: 和 dispatch_async() 有什么区别? 【发布时间】:2012-03-09 06:38:41 【问题描述】:

我在修改线程内的视图时遇到问题。我尝试添加一个子视图,但显示需要大约 6 秒或更长时间。我终于让它工作了,但我不知道具体如何。所以我想知道它为什么起作用以及以下方法之间有什么区别:

    这很有效 - 立即添加了视图:
dispatch_async(dispatch_get_main_queue(), ^
    //some UI methods ej
    [view addSubview: otherView];

    这需要大约 6 秒或更长时间才能显示:
[viewController performSelectorOnMainThread:@selector(methodThatAddsSubview:) withObject:otherView
    waitUntilDone:NO];
    NSNotification 方法 - 还花了大约 6 秒时间来显示观察者在我想修改的 viewController 中,并与添加子视图的方法配对。
[[NSNotificationCenter defaultCenter] postNotificationName:
 @"notification-identifier" object:object];

作为参考,这些是在 ACAccountStore 类的 CompletionHandler 中调用的。

accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) 
    if(granted) 
        // my methods were here
    

【问题讨论】:

当你说performSelectorOnMainThread:不起作用时,它是怎么失败的?你收到错误信息了吗?是运行时错误还是编译错误?如果你没有收到错误,你怎么知道它失败了? addSubview: 是您唯一使用的接触 UI 元素的方法,还是还有其他方法? @AndrewMadsen 我忘了说它有效,但显示需要大约 6 秒或更长时间。 @MattWilding 是的。在我的测试过程中,我尝试添加一个空白子视图,但仍然需要大约 6 秒才能在主线程上显示 nsnotification 和 performselector。 【参考方案1】:

默认情况下,-performSelectorOnMainThread:withObject:waitUntilDone: 仅调度选择器以默认运行循环模式运行。如果运行循环处于另一种模式(例如跟踪模式),它不会运行,直到运行循环切换回默认模式。您可以使用变体 -performSelectorOnMainThread:withObject:waitUntilDone:modes: 解决此问题(通过传递您希望它运行的所有模式)。

另一方面,dispatch_async(dispatch_get_main_queue(), ^ ... ) 将在主运行循环将控制流返回给事件循环后立即运行该块。它不关心模式。因此,如果您也不想关心模式,dispatch_async() 可能是更好的选择。

【讨论】:

什么是模式,什么时候应该关注它们? @MattDiPasquale:在 ios 上,你基本上可以忽略它们,runloop 通常总是在默认模式下运行。在 OS X 上,您可能会看到另外 3 种模式,NSConnectionReplyModeNSModalPanelRunLoopModeNSEventTrackingRunLoopMode。如果您有兴趣,可以查找这些文档。 请注意,在 iOS 中 UITrackingRunLoopMode 用于某些触摸事件跟踪(当 UIScrollView 正在跟踪触摸移动时)。 这不是真的,它使用了默认的运行循环模式。 performSelectorOnMainThread 是这样记录的:“此方法使用 常见的运行循环模式 - 在主线程的运行循环上排队消息也就是与 NSRunLoopCommonModes constant相关的模式。" 如果您在主线程 dispatch_async 块中启动模型对话窗口,则在 OS 10.9 上通过鼠标滚轮滚动将不起作用(可能 b/c 仅在默认运行循环模式下有效)。此外,未捕获的异常将始终终止程序,这在生产代码中很糟糕(唯一的出路是在您编写的每个 dispatch_async-block 中嵌套一个 try-catch)。由于这些原因,我们更喜欢 performSelectorOnMainThread。我们创建了一个内部调用 performSelectorOnMainThread 的 dispatch_async_main 变体,因此我们可以继续使用漂亮的块语法。【参考方案2】:

这可能是因为performSelectorOnMainThread:withObject:waitUntilDone: 使用常见的运行循环模式将消息排队。根据Apple's Concurrency Programming Guide,主队列会将排队的任务与来自应用程序运行循环的其他事件交错。因此,如果事件队列中还有其他事件需要处理,调度队列中的排队块可能会先运行,即使它们是稍后提交的。

This article 是对performSelectorOnMainThreaddispatch_async 的绝佳解释,它也回答了上述问题。

【讨论】:

【参考方案3】:

你试过PerformSelectorOnMainThreadwaitUntilDone=YES

例如:

代码:

[viewController performSelectorOnMainThread:@selector(methodThatAddsSubview:) withObject:otherView waitUntilDone:YES];

我认为这可能会解决为什么 PerformSelectorOnMainThread 需要这么长时间才能回复的问题。

【讨论】:

以上是关于主队列上的 performSelectorOnMainThread: 和 dispatch_async() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

从主队列的回调中更新 UIView 上的 UILabel

主队列上的 dispatch_sync 与 dispatch_async

RNFetchBlob 需要主队列设置

dispatch

Alamofire RequestRetrier,在主操作队列上调用完成块可以吗?

js执行队列顺序