Skip to content
advanced Phase 11 · Performance & Optimization

App Startup Optimization

Reduce launch time: lazy initialization, pre-warming, and dtrace analysis.

45m
2 problems
Topic Progress 0%

Launch Phases

iOS App Launch Sequence

When a user taps your app icon, iOS executes a series of steps before your code runs:

  1. pre-main phase: dyld loads the dynamic libraries, rebase and bind symbols, run initializers
  2. main(): Your main function executes
  3. UIKit setup: UIApplication and AppDelegate or App lifecycle
  4. First frame: Your initial view appears on screen

pre-main Phase

Before main() is called, dyld (dynamic linker) performs:

  • Loading Mach-O binary and all linked frameworks
  • Rebasing: adjusting pointers for ASLR (Address Space Layout Randomization)
  • Binding: resolving external symbols
  • Running static constructors and load methods

This phase is affected by the number and size of frameworks you link.

main() to First Frame

After main(), your app must:

  • Initialize AppDelegate or SwiftUI App
  • Set up the window and root view controller
  • Load initial data
  • Render the first frame

The time from app launch to first frame should be under 400ms for a good user experience. Apple recommends under 200ms.

Impact on Users

Slow startup is one of the top reasons users abandon apps. A 1-second delay in launch time can reduce user engagement by 20%. The system also terminates apps that take too long to launch, showing the user a blank screen.

Measuring Startup Time

Using Instruments App Launch Template

The most accurate way to measure launch time is with Instruments. Select the App Launch template which shows:

  • Time to first frame
  • Pre-main time breakdown
  • dyld loading time
  • Runtime initialization time

Using DTrace for Quick Measurement

From the terminal, measure time from process start to main:

dtrace -n 'proc:::exec-success /execname == "MyApp"/ { self->start = timestamp; } \
  pid$target::main:entry /self->start/ { printf("Time to main: %d ms", (timestamp - self->start) / 1000000); exit(0); }' \
  -c /path/to/MyApp.app/MyApp

Adding Custom Timestamps

Log timestamps at key initialization points:

@main
struct MyApp: App {
    init() {
        let launchStart = CFAbsoluteTimeGetCurrent()
        // Heavy initialization
        setupDependencies()
        setupAnalytics()
        let launchEnd = CFAbsoluteTimeGetCurrent()
        print("Launch init: \(launchEnd - launchStart)ms")
    }
    
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

Testing on Older Devices

Always test on the oldest device your app supports. Launch time on an iPhone SE is significantly different from an iPhone 15 Pro. Test with:

  • Cold launch (device restarted)
  • Warm launch (app was in background)
  • Release builds only (Debug builds are much slower)

Optimization Strategies

Lazy Initialization

Defer expensive work until it is actually needed:

// Bad: Initialize everything at launch
class AppContainer {
    let analytics = AnalyticsEngine()
    let network = NetworkService()
    let database = DatabaseManager()
    let imageCache = ImageCache()
}

// Good: Lazy initialization
class AppContainer {
    lazy var analytics = AnalyticsEngine()
    lazy var network = NetworkService()
    lazy var database = DatabaseManager()
    lazy var imageCache = ImageCache()
}

Deferred Non-Essential Setup

Move non-critical initialization after the first frame:

func applicationDidBecomeActive(_ application: UIApplication) {
    // First frame has already rendered
    DispatchQueue.main.async {
        self.setupCrashReporting()
        self.setupAnalytics()
        self.checkForUpdates()
    }
}

Reduce Framework Count

Each linked framework adds to pre-main time. Audit your frameworks:

  • Remove unused framework dependencies
  • Use weak linking for optional frameworks
  • Consider modularizing your app to load features on demand

Optimize Dynamic Libraries

  • Prefer static libraries over dynamic when possible
  • Merge small frameworks into a single module
  • Use -ObjC linker flag only when necessary

Binary Size Optimization

Smaller binaries load faster:

  • Enable Link-Time Optimization (LTO)
  • Strip unused symbols
  • Use Asset Catalogs for images instead of bundle resources
  • Enable App Thinning in your scheme settings

Deferred Storyboard Loading

If using storyboards, mark initial view controllers as not immediate and load them manually:

// In Info.plist, set UIApplicationSceneManifest
// Use SwiftUI App lifecycle instead of storyboard for fastest launch
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

SwiftUI App lifecycle is faster than UIKit storyboard loading because it avoids storyboard parsing overhead.

Quiz

1. What happens during the pre-main phase of app launch?

Question 1 options

2. What is Apple recommended maximum time to first frame?

Question 2 options

3. How does lazy initialization help startup time?

Question 3 options

4. Which app lifecycle is fastest for launch time?

Question 4 options

Flashcards

Question

What are the three phases of iOS app launch?

Answer

pre-main (dyld loading), main() execution, and UIKit setup to first frame.

Question

What reduces pre-main launch time?

Answer

Reducing the number of linked frameworks, using static libraries, and minimizing load methods.

Question

What is the benefit of lazy var over let for initialization?

Answer

lazy var defers initialization until first access, while let initializes immediately at object creation time.

Revision Notes

Key Takeaways

  • 1. The pre-main phase loads all linked frameworks and is affected by framework count
  • 2. Target under 400ms from launch to first frame
  • 3. Lazy initialization defers expensive work until needed
  • 4. Move non-critical setup to after the first frame renders
  • 5. Always test launch time on the oldest supported device

Interview Tips

  • Explain the pre-main phase and what affects its duration
  • Describe techniques for reducing launch time
  • Discuss how lazy initialization improves startup
  • Walk through measuring launch time with Instruments

Cheat Sheet

Startup Optimization Quick Reference

  • Pre-main: dyld loads frameworks (reduce linked frameworks)
  • main() to first frame: target under 400ms
  • Use lazy initialization for non-essential objects
  • Defer analytics and crash reporting after first frame
  • Prefer SwiftUI App lifecycle over storyboards
  • Test on oldest supported device with cold launch