0

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?

Dzondzula
  • 81
  • 1
  • 8

1 Answers1

1

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.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • hey @Dávid , the async solution I wanted to apply was reccomened after getting an error of using async in sync context: "Add 'async' to function 'scene(_:willConnectTo:options:)' to make it asynchronous" why would we be provided with this fix if it breaks the protocol? is it because it is generic fix compiler provides for all methods?Or do we always need to use Task{} for async operations inside scene method? – Dzondzula Apr 27 '23 at 13:46
  • @Dzondzula the auto fix doesn't know that the method you added is a protocol method. How could it know? That's just a general fixit for trying to call async code from a sync context. – Dávid Pásztor Apr 27 '23 at 13:47
  • As for calling `async` code from a `sync` method - you do need to wrap the `async` calls in a `Task` if you want to make them from a synchronous context – Dávid Pásztor Apr 27 '23 at 13:47
  • Thank you David so in order to use any async method inside iOS delegate methods we must implement Task where we will place our async methods – Dzondzula Apr 27 '23 at 14:02
  • @Dzondzula this isn't specific to delegate methods, this is the general way to call `async` methods from synchronous methods. But yes, for this particular delegate method, which is synchronous, you have to wrap `async` calls in a `Task`. – Dávid Pásztor Apr 27 '23 at 14:13