skip to Main Content

We had an app in production which was reporting very high time to interact(tti) for ios 15 prewarm.

TTI = timewhenViewController is loaded – mainStartTime

mainStart time is measured inside AppDelegate.swift’s willFinishLaunchingWithOptions method like this

mainStartTime = Date()

and when first view controller is loaded we measure tti as

tti = -(mainStartTime.timeIntervalSinceNow)

We observed that for prewarm scenarios mainStartTime was coming very early (approx 2 hours before user even launches the app).

I checked online but found no documentation.
Just wanted to know can it possibly happen that prewarm is calling willFinishLaunchingWithOptions method while prewarming the app.

2

Answers


  1. There is poor documentation about Prewarming:

    Prewarming executes an app’s launch sequence up until, but not including, when main() calls UIApplicationMain(::::). https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence#3894431

    But prewarming can call both willFinishLaunchingWithOptions and didFinishLaunchingWithOptions from my tests on my iPhone 13 with iOS 15.0.

    I’ve created a test app which logs all calls to a string array:

    main.swift

    var logMessages = [String]()
    
    func addLogMessage(_ text: String) {
        let date = Date().formatted(date: .omitted, time: .standard)
        logMessages.append("(date): (text)")
    }
    
    autoreleasepool {
        addLogMessage("main.swift")
        UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
    }
    

    AppDelegate.swift

    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        override init() {
            super.init()
            addLogMessage("AppDelegate.init")
        }
        
        func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
            addLogMessage("willFinishLaunchingWithOptions")
            return true
        }
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            addLogMessage("didFinishLaunchingWithOptions")
            return true
        }
    
        func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) {
            addLogMessage("applicationProtectedDataDidBecomeAvailable")
        }
    
        func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
            addLogMessage("configurationForConnecting")
            return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
        }
        …
    }
    

    SceneDelegate.swift

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
        var window: UIWindow? {
            didSet {
                addLogMessage("SceneDelegate.didSet window")
            }
        }
    
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            addLogMessage("SceneDelegate.willConnectTo")
            guard let _ = (scene as? UIWindowScene) else { return }
        }
        …
    }
    
    

    Then did the following:

    1. Launch the app
    2. Use it briefly
    3. Force quit the app
    4. Lock my device and leave it for ~30 minutes
    5. Unlock the device
    6. Launch the app again

    My logs:

    12:28:36 AM: main.swift
    12:28:36 AM: AppDelegate.init
    12:28:36 AM: willFinishLaunchingWithOptions
    12:28:36 AM: didFinishLaunchingWithOptions
    
    12:58:15 AM: applicationProtectedDataDidBecomeAvailable
    12:58:15 AM: configurationForConnecting
    12:58:15 AM: SceneDelegate.didSet window
    12:58:15 AM: SceneDelegate.willConnectTo
    12:58:15 AM: viewDidLoad
    

    As you can see the system launched my app (prewarm) at 12:28:36 and held in background until my manual start at 12:58:15 and after gave access to protected data and loaded UI.

    Login or Signup to reply.
  2. Apple’s documentation is incorrect, here is the behaviour observed on iOS 15:

    UIApplicationMain() always runs, including during prewarming.

    What happens after this depends on whether your app uses the UIScene life-cycle.

    • For apps that do support scenes:

      • application:didFinishLaunchingWithOptions: may be called (doesn’t always happen)
      • scene:willConnectToSession:options: is not called – in fact the SceneDelegate is not created until the app is opened.
    • For apps that do not support scenes:

      • application:didFinishLaunchingWithOptions: is not called.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search