ios 15 prewarming causing appwillLaunch method when prewarm is done

Viewed 1936

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

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.

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.
Related