为SceneDelegate的scene方法添加async后无法设置rootViewController?
我在iOS项目中使用Coordinator模式,需要通过异步操作设置window.rootViewController。最初给SceneDelegate的scene(_:willConnectTo:options:)方法添加了async修饰符,代码如下:
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? var coordinator : MainCoordinator! // 添加了async修饰符的scene方法 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) async { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(frame: UIScreen.main.bounds) window?.windowScene = windowScene coordinator = MainCoordinator(window: window!) await coordinator.start() } }
MainCoordinator中设置根视图控制器的相关代码:
lazy var rootViewController: UIViewController = UIViewController() lazy var tabCoordinator: TabCoordinator = TabCoordinator(tabBarController: rootViewController as! UITabBarController) func start() async { if UserDefaults.standard.bool(forKey: "Logged") { await showTabVC() } else { showLoginVC() } } func showTabVC() async { rootViewController = tabBarController tabCoordinator = TabCoordinator(tabBarController: rootViewController as! UITabBarController) let tabVC = await tabCoordinator.start() window.rootViewController = tabVC window.makeKeyAndVisible() }
TabCoordinator的start方法标记了@MainActor以确保UI更新正常,但运行后出现黑屏。
后来我移除了scene方法的async修饰符,把coordinator.start()放入Task{}中,功能恢复正常:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(frame: UIScreen.main.bounds) window?.windowScene = windowScene coordinator = MainCoordinator(window: window!) Task{ await coordinator.start() } }
想知道为什么给scene方法添加async会导致黑屏问题?
系统对SceneDelegate回调的执行逻辑限制
iOS系统调用scene(_:willConnectTo:options:)时,要求这个方法同步完成——它需要在方法执行结束后确认窗口和根视图控制器的初始化状态。给方法加上async修饰符后,系统会将其视为异步任务,方法会立即挂起并返回,此时窗口还没设置根视图控制器,系统就判定初始化完成,自然显示黑屏。异步上下文的差异
直接给系统回调方法加async,其运行上下文和手动创建Task的上下文不同。系统回调的异步方法不一定会在MainActor上执行(即便后续逻辑标记了@MainActor),而手动创建的Task默认继承当前上下文(SceneDelegate的方法本身在主线程执行,所以Task也会在主线程上下文运行),能保证UI更新时机正确。窗口初始化的时机问题
当scene方法为异步时,执行到await coordinator.start()会挂起,此时窗口已创建但未设置根视图控制器,系统可能在挂起阶段就完成了场景初始化流程,没有等待异步操作结束,导致窗口没有关联可见的视图控制器,最终出现黑屏。而用Task包裹时,scene方法会同步完成窗口创建和Coordinator初始化,之后在后台异步执行coordinator.start(),此时系统已确认窗口存在,后续设置根视图控制器的操作能正常生效。
内容的提问来源于stack exchange,提问作者Dzondzula

