iosNudge

How do you measure and improve app launch time?

HardAsked oftenPerformanceApp lifecycle 3 min read

TL;DR

Launch time is the time from tapping the icon to the first frame the user can use. Apple suggests aiming for the first frame in about 400 ms. Measure cold launches on a real device in a Release build. Fix: fewer dynamic frameworks, no heavy static initializers, and do only first-screen work in didFinishLaunching. Defer the rest.

Types of launch

TypeWhat happensSpeed
ColdNot in memory, after reboot or long timeSlowest
WarmProcess gone, but parts still cached in memoryFaster
ResumeApp was suspended, just comes backNot a launch

Test the cold case. It is what users feel after the system frees memory.

The phases

Tap -> [System: dyld loads binaries, runs static initializers] -> main()
    -> UIApplication / App init -> didFinishLaunching -> scene setup
    -> first frame rendered -> app is responsive (extended launch)

Before main (pre-main)

  • dyld loads and links your binary and every dynamic framework. Each dynamic framework costs time.
  • Static initializers run: Objective-C +load methods, C++ static constructors.

After main

  • Your App.init, application(_:didFinishLaunchingWithOptions:), root view creation and first layout.

How to measure

  1. Instruments > App Launch template. Shows each phase and what blocks the main thread.
  2. XCTest metric in a UI test, to catch regressions in CI:
func testLaunchPerformance() {
    measure(metrics: [XCTApplicationLaunchMetric()]) {
        XCUIApplication().launch()
    }
}
  1. Xcode Organizer > Launch Time: real data from users' devices.
  2. MetricKit: MXAppLaunchMetric gives launch histograms from the field.
  3. Signposts around your own startup steps:
import os
let signposter = OSSignposter(subsystem: "com.example.app", category: "Launch")

let state = signposter.beginInterval("LoadConfig")
loadConfig()
signposter.endInterval("LoadConfig", state)

How to improve

1. Do less in didFinishLaunching

func application(_ app: UIApplication,
                 didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    CrashReporter.start()                 // needed early
    // Analytics, remote config, prefetching: later
    Task(priority: .utility) {
        await Analytics.start()
        await RemoteConfig.refresh()
    }
    return true
}

Ask for each line: "Does the first screen need this?" If not, defer it or make it lazy.

2. Never block the main thread

No synchronous network calls, big file reads or database migrations before the first frame. If a migration is needed, show a simple screen first.

3. Fewer dynamic frameworks

Prefer static linking. Swift packages are static by default. Xcode 15+ supports mergeable libraries: dynamic for fast debug builds, merged into the app for release.

4. Remove static initializers

Avoid +load in Objective-C and global C++ objects. Use lazy static let in Swift. Swift globals are lazy already.

5. Make the first screen cheap

  • Load placeholder or cached data first, then refresh.
  • Avoid huge storyboards and heavy view hierarchies on the first screen.
  • Do not decode big images on launch.

6. Watch third-party SDKs

Many SDKs do work on start. Measure each one. Start them later if you can.

What does not count

iOS sometimes pre-warms apps: it starts the process before the user taps. Pre-warming can run your launch sequence up to UIApplicationMain and then pause. So do not start timers or important work in static initializers, and measure launch from main, not from process start.

Common follow-up questions

  • What is the watchdog limit? If the app takes too long to launch (roughly 20 seconds), the system kills it. Crash reports show code 0x8badf00d.
  • Why is Debug launch slower? No optimization, plus debugger overhead. Always measure Release.
  • What is order file optimization? Reordering symbols so startup code sits on fewer memory pages. Fewer page faults on launch.
  • Do launch screens make launch faster? No. They only make it feel faster. Keep them static and simple.