在后台线程上创建 UIViewController 可以吗?

Posted

技术标签:

【中文标题】在后台线程上创建 UIViewController 可以吗?【英文标题】:Is it OK to create a UIViewController on a background thread? 【发布时间】:2015-11-03 23:37:18 【问题描述】:

相关:Is it ok to create a UIView on a background thread?

这个后台线程代码安全吗?

let viewController = MyViewController(nibName: nil, bundle: nil)
viewController.title = "My Title"
viewController.myProperty = true
dispatch_async(dispatch_get_main_queue(), 
    self.navigationController?.pushViewController(viewController, animated: true)
)

【问题讨论】:

谢谢! :-) 那么,MyViewController(nibName: nil, bundle: nil) 不会触发任何 UI 更新? viewController.myProperty = true 没有副作用。这只是一个简单的属性。 【参考方案1】:

看来@ozgur 的答案现在已经过时了。如果您尝试在最新版本的 Xcode(11.5 在撰写本文时)的后台线程中创建 UIViewController,那么您将收到以下错误:

-[UIViewController init] must be used from main thread only

【讨论】:

【参考方案2】:

这取决于实例变量实际在做什么。一般规则是后台线程运行的代码不应触发任何 UI 更新,例如 view.addSubview(..)view.setNeedsLayout 等,然后使用后台线程与视图控制器一起玩是安全的。

另一个例子是导航控制器。例如,一旦将视图控制器推送到导航堆栈上,即使更新 viewController.title 也可能很危险,因此您应该确保 viewController.myProperty = true 不会触发任何 UI 更新。就个人而言,我会在主线程中执行以下任务以确保安全:

dispatch_async(dispatch_get_main_queue(), 
  viewController.title = "My Title"
  viewController.myProperty = true
  ...
)

长话短说,您可以在后台线程中初始化新的 UIView 或 UIViewController(或任何 UIResponder),但是,您应该更改其任何属性以在主线程中触发 UI 更新。所以在后台创建但在主线程中更新。

【讨论】:

我相信在主队列上处理一些属性并不足以绕过这里的问题。仅将视图控制器推送到导航堆栈是应该在主队列上完成的事情;而所有视图控制器生命周期方法都将被调用(以及视图控制器视图对象的layoutsubviews 等内容)。总而言之,不安全! @antonio 我认为您在理解您正在阅读的内容方面存在问题。我们基本上是在说同样的话。在任何线程中初始化视图控制器都是非常安全的,但是一旦 UI 更新开始起作用,您必须始终在主队列上执行此操作。创建视图控制器不会触发任何有关其生命周期的方法,例如 viewDidLoadviewWillAppearviewDidLayoutSubviews 我不是在谈论创造;我谈论的是推入导航堆栈。 推入导航堆栈是 UI 更新,因此必须在主队列中处理。

以上是关于在后台线程上创建 UIViewController 可以吗?的主要内容,如果未能解决你的问题,请参考以下文章

在后台线程上创建一个视图,将其添加到主线程上的主视图

我无法使用非主 MOC 在后台线程上创建 NSManagedObject 的新实例

在后台创建的 NSManagedObject 变成了主线程上的错误

在私有/后台队列上创建 NSManagedObjectContext:怎么办?

在异步后台线程上运行 CADisplayLink 的正确方法?

在后台线程中更新托管对象上下文