Article

Reducing Your App's Launch Time

Create a more responsive experience with your app by minimizing time spent in startup.

Overview

A user’s first experience with an app is the wait while it launches. The launch screen shows the user that the app is preparing itself for use, but it should also be ready to help the user with a task as soon as possible. An app that takes too long to launch may frustrate the user, and the iOS watchdog process may even terminate it if it takes too long to respond. Users may launch an app many times in a day if it’s part of their regular workflow, and a long launch time causes delays for users waiting to perform a task.

When the user taps an app’s icon on their Home screen, iOS prepares the app for launch before handing control over to the app process. The app then runs code to get ready to draw its UI to the screen. Even after the app’s UI is visible, the app may still be preparing content or replacing an interstitial interface (for example, a loading spinner) with the final controls. Each of these steps contributes to the total perceived launch time of the app, and you can take steps to reduce their duration.

Measure Your App's Launch Time

In Xcode’s Metrics organizer, switch between the median and 95th percentile launch times to understand what your users experience in common and extreme cases. The observed launch time varies with external factors, including the amount of memory in use by other processes and the frameworks that other apps have already loaded. Measure your app’s launch time in different situations to see how these factors affect the experience. Here are some examples of different situations to test:

  • Switch the device on, unlock it for the first time, and then launch your app.

  • Force quit your app, and then launch it.

  • Open other apps, and then launch yours.

  • Use a very large app—for example, one that works with many graphical resources or live camera input—and then launch your app.

Profile Your App's Launch Time

Profile your app in Instruments by using the App Launch template. Instruments runs your app for five seconds, during which it gathers a time profile and thread-state trace. Use the time profile to identify the code your app is running during launch. Use the thread-state trace to find times when threads are active or blocked, and discover the reasons that threads are blocked.

Image showing a thread-state trace in Instruments, and a description of a blocked thread.

UIKit draws views and handles user events on the main thread, so that thread must be available to draw the first frame when the app has finished launching. In the Instruments thread trace, time that the main thread spends running or preempted is time that it cannot draw views or respond to user input events.

For a different view of app launch, profile the app using the Time Profile template. The App Life Cycle timeline divides activity during app launch into process initialization, UIKit initialization, UIKit initial scene rendering, and initial frame rendering.

Image showing the App Life Cycle timeline in Instruments.

Identify Launch-Time Factors: Dynamic Library Loading

The dynamic loader (dyld) loads the app’s executable file, and examines the Mach load commands in the executable to find frameworks and dynamic libraries that the app needs. It then loads each of the frameworks into memory, and resolves dynamic symbols in the executable to point to the appropriate addresses in the dynamic libraries.

Each additional framework that your app loads adds to the launch time. Although dyld caches a lot of this work in a launch closure when the user installs the app, the size of the launch closure and the amount of work done after loading it still depend on the number and sizes of the libraries loaded. You can reduce your app’s launch time by limiting the number of frameworks you embed. Frameworks that you import or add to your app’s “Linked Frameworks and Libraries” setting in the Target editor in Xcode count toward this number.

Identify Launch-Time Factors: Static Initializers

Certain code in an app must run before iOS runs your app’s main() function, adding to the launch time. This code includes:

  • C++ static constructors.

  • Objective-C +load methods defined in classes or categories.

  • Functions marked with the clang attribute __attribute__((constructor)).

  • Any function linked into the __DATA,__mod_init_func section of an app or framework binary.

Where possible, move the code to a later stage of the app’s life cycle, after the app has finished launching but before the results of the work are needed. In Instruments, the Static Initializer Calls instrument measures the time your app spends running static initializers.

Image showing a timeline of static initializer calls in Instruments.

Identify Launch-Time Factors: UIKit Lifecycle Methods

UIKit initializes an instance of your app delegate class (the class that conforms to the UIApplicationDelegate protocol) and sends it the application:willFinishLaunchingWithOptions: and application:didFinishLaunchingWithOptions: messages. UIKit sends these messages on the main thread, and time spent executing code in these methods increases your app’s launch time. Do only the work necessary to prepare your app’s initial display in these methods; defer other tasks to more appropriate times in the app’s life cycle.

Defer synchronization of the data model with a network service until the app is running, if it makes sense to show stale content to the user while the content is being refreshed. Move the synchronization to an asynchronous background queue. Register a background task to fetch updates from the network service, to reduce both the staleness of data on launch and the amount of work needed to bring it up to date.

Initialize nonview functionality, such as persistent storage and location services, on first use rather than on app launch. Retrieve only the data necessary to display your app’s initial view. Pay attention to whether your app is restoring state, and prepare the data needed to display the view that's being restored. If no state is being restored, prepare only the default initial view. For example, a photo gallery app might show a collection of image thumbnails by default and let a user pick a photo to get a detailed view. If the app is launching with no restored state, it only needs to show a placeholder for a screenful of thumbnails and fill them in with real image thumbnails once the app has finished launching. It doesn't need to load the full, detailed images until the user taps one of the thumbnails.

Initialize a restricted subset of the app’s behavior that's known to be viable on initial launch. For example, a task manager app can let the user create a new task on launch, even if the app hasn't yet retrieved all of the user’s existing tasks from its persistent storage or network service.

Identify Launch-Time Factors: Drawing Initial View Hierarchy

The Xcode Metrics organizer and MetricKit both use the time to first frame as the measurement of launch time, including the time required to draw the views that are displayed on that first frame. Adding views to the view hierarchy is necessarily confined to the main thread, and thus a more complicated view hierarchy with more views takes longer to render than a simple hierarchy.

Reducing the complexity of your app’s initial view improves the load time, as does replacing custom views that override drawRect: with standard views. Where you need custom drawing, pay attention to the rectangle passed to draw(_:) and only render parts of the view within that rectangle. Doing so avoids decoding images and computing colors, coordinates, and drawing commands in parts of the view that aren’t rendered to the screen.

Track Additional Startup Activities

The launch-time metric measures the time from the user tapping the app icon on their Home screen to the app drawing its first frame to the screen. Drawing the default.png or launch-screen storyboard happens during this time, and its appearance doesn’t end the launch-time counter.

If your app still has to run code after it has drawn its first frame, but before the user can begin using the app, that time doesn’t contribute to the launch-time metric. Extra startup activities still contribute to the user’s perception of the app’s responsiveness.

To track additional startup activities, create an OSLog object in your app with the category pointsOfInterest. Use the os_signpost function to record the beginning and end of your app’s preparation tasks, as shown in the following example:

class ViewController: UIViewController {
    static let startupActivities:StaticString = "Startup Activities"
    let poiLog = OSLog(subsystem: "com.example.CocoaPictures", category: .pointsOfInterest)

    override func viewDidLoad() {
        super.viewDidLoad()
        os_signpost(.begin, log: self.poiLog, name: ViewController.startupActivities)
        // do work to prepare the view
        os_signpost(.end, log: self.poiLog, name: ViewController.startupActivities)
}

In Instruments, Points of Interest displays the signposts in its timeline. You can use this information to correlate activity in your app with the app’s additional startup tasks.

Screenshot showing the Points of Interest instrument, with a timeline of regions beginning and ending during an app's additional startup activities.