swiftasync-awaituikituiscenedelegate

Using async with SceneDelegate method is not setting the rootVC?


Im using Coordinator pattern and therefore setting window.rootViewController I need to perform some async operations.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var coordinator : MainCoordinator!
//I have added the async to scene func
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 call for setting rootVC

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()
}

As the TabCoordinator class has start method that returns UIViewController it is marked with @MainActor so the UI can update as well after finishing with start method.

With code structured like this after running it I end up with black screen and I don't know why?

I have managed to make it work if I change the scene method from scene delegate by removing the async from scene method and placing coordinator.start inside Task{} everything works well:

 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()
}

What is the problem that async cause to scene method as I consider this as just another way that is less pleasant to read?


Solution

  • Adding async to a method changes its type signature.

    func scene(
      _ scene: UIScene, 
      willConnectTo session: UISceneSession, 
      options connectionOptions: UIScene.ConnectionOptions
    ) async {
    

    and

    func scene(
      _ scene: UIScene, 
      willConnectTo session: UISceneSession, 
      options connectionOptions: UIScene.ConnectionOptions
    ) {
    

    are two different methods.

    The UIWindowSceneDelegate protocol requires the synchronous method, so adding async to it means that the method isn't matching the protocol requirement anymore, hence it won't be called by the system.

    You don't get a compiler error for this due to the fact that the method is an optional protocol requirement, so not implementing it doesn't actually break the protocol conformance.