What happens when an iOS app opens, which lifecycle callbacks are executed, and where startup measurement should begin and end. An iOS application does not have one launch path. A person may tap the app icon after restarting their phone, return to an app that is still suspended in memory, open a Universal Link from Safari, select a Home Screen quick action, or tap a notification that points to a particular screen. All of these actions appear to have the same result: the application opens. Internally, however, they can execute very different combinations of system and application code. This distinction matters whenever we discuss startup performance. If we mix a process launch with a resume, or measure an icon launch while most users arrive through notifications, we can collect an accurate number that describes the wrong experience. In one of the production applications I worked on, startup time had gradually grown to approximately nine seconds. The problem was not one exceptionally slow function. Before we could optimize anything, we had to answer more fundamental questions: What did we mean by startup? Which launch scenario were we measuring? Where did that scenario begin and end? Which lifecycle callbacks belonged to the process, and which belonged to a scene? When did the application become visible, and when did it become useful? After defining the lifecycle and instrumenting the critical path, we redesigned the startup pipeline and eventually reduced startup time to approximately three seconds. This article is the first part of iOS Startup Performance in Practice, a six-part series about understanding, measuring, optimizing, and monitoring startup performance in production iOS applications: Understanding Launch Types and Entry Points Measuring App Launch Time Correctly Finding Bottlenecks from dyld to the First Frame Designing a Scalable Startup Architecture From the First Frame to an Interactive UI Preventing Regressions in Production In this first part, we will build the model needed for everything that follows. “App launch” can mean four different things Developers often divide startup into cold and warm launch. That distinction is useful, but incomplete. For performance work, I use four categories: Cold launch Warm launch Resume Prewarmed launch The most important boundary is not between cold and warm. It is between a new process launch and a resume of an existing process. Apple makes the same distinction in its Optimizing App Launch session: cold and warm launches both spawn the app, whereas a resume returns to an app that has already launched. Cold launch This is normally the most expensive and least cache-friendly launch scenario. A cold launch begins when the application process does not exist and the system cannot reuse enough recently cached application state to avoid reading significant parts of the executable and its dependencies from storage. Typical situations include: the first launch after a device reboot; the first launch after an application update; launching an app that has not been used for a long time; launching after memory pressure has evicted relevant pages and caches. Conceptually, the system has to perform the following work: Create the process. Load the executable and required libraries. Perform runtime initialization. Enter the application at `main`. Initialize `UIApplication` and the app delegate. Connect one or more scenes. Build the initial interface. Render the first frame. Warm launch A warm launch also creates a new process and executes the application launch lifecycle again. The difference is that the system may still have recently used code, data, or supporting services available in memory or system caches. For example, force-quitting an application and immediately opening it again normally creates a new process, but it does not reliably reproduce a cold launch. The second launch is likely to benefit from a warmed system state. During a warm launch: the process is created again; `main` executes again; the app delegate is created again; launch methods execute again; the scene is connected again; the first UI still has to be constructed. The callback sequence is therefore much closer to a cold launch than to a resume. The timings are different, but the application-level path is largely the same. Resume A resume occurs when the application process still exists and the user returns to an existing scene, usually from the Home Screen or app switcher. The process may have been suspended, which means it was kept in memory but was not executing application code. When the user returns, iOS resumes the process and moves the scene toward the active state. During a normal resume: the executable is not loaded again; `main` does not execute again; the app delegate is not recreated; `application(_:didFinishLaunchingWithOptions:)` does not execute again; `scene(_:willConnectTo:options:)` does not execute for an existing scene; foreground and activation callbacks do execute. Calling this a warm launch hides a crucial architectural difference. Code inside `didFinishLaunching` can affect cold and warm launch, but it cannot explain a slow resume because it does not run during that transition. Prewarmed launch iOS may start some launch work before the user explicitly opens the app. This is commonly called prewarming. It allows the system to perform part of process initialization in advance and shorten the user-perceived interval after the tap. Prewarming is an optimization controlled by the operating system, not an entry point that application code should depend on. Your startup implementation still needs to be correct when the system chooses not to prewarm the process. It also introduces an important measurement detail: the full process lifetime and the interval perceived by the user may begin at different moments. Apple exposes an optimized time-to-first-draw metric for this reason in current MetricKit APIs. Launch types at a glance Scenario New process `main` executes App launch callbacks execute Existing scene reused Cold launch Yes Yes Yes No Warm launch Yes Yes Yes No Resume No No No Yes New scene in an existing process No No No No; a new scene is created Prewarmed launch The process may start early Yes, possibly before the user action Yes Depends on the requested scene There is no public Boolean property that reliably tells application code, “this was a cold launch.” Cold and warm describe the surrounding system state, not different app delegate APIs. To study them, control the experiment instead of branching application logic. Process lifecycle and scene lifecycle are different layers Modern iOS applications have at least two lifecycle layers: the application lifecycle, associated with the process, `UIApplication`, and `UIApplicationDelegate`; the scene lifecycle, associated with one instance of the UI, `UIScene`, and `UISceneDelegate`. An application process can own several scenes. On iPadOS, for example, the same application may show multiple windows. Those scenes share one process and one app delegate, but each scene has its own delegate, activation state, navigation state, and entry context. This leads to two rules that are easy to miss: > Creating a scene does not necessarily mean launching a process. > Activating an existing scene does not mean launching either a process or a scene. At launch, UIKit creates the application object and app delegate, starts the main event loop, and then connects scenes. Apple describes this separation in Responding to the launch of your app and its Scenes documentation. For performance work, this means we need separate intervals for process launch, scene connection, route execution, and resume. What executes during a new process launch? For a UIKit app that uses the scene-based lifecycle, a simplified foreground launch looks like this: User or system requests the app ↓ Process creation and system-side setup ↓ dyld loads the executable and dependencies ↓ Runtime and static initialization ↓ main / UIApplicationMain ↓ AppDelegate initialization ↓ application(_:willFinishLaunchingWithOptions:) ↓ application(_:didFinishLaunchingWithOptions:) ↓ application(_:configurationForConnecting:options:) ↓ scene(_:willConnectTo:options:) ↓ sceneWillEnterForeground(_:) ↓ sceneDidBecomeActive(_:) ↓ Initial frame becomes visible This diagram is deliberately simplified. State restoration, background launches, SwiftUI, and multiple-scene behavior can add or alter steps. It is a model of the major ownership boundaries, not a promise that every observable event will always appear in an identical order. The app delegate owns process-wide initialization: import OSLog import UIKit private let lifecycleLog = Logger( subsystem: Bundle.main.bundleIdentifier ?? "ExampleApp", category: "Lifecycle" ) @main final class AppDelegate: UIResponder, UIApplicationDelegate { override init() { lifecycleLog.info("AppDelegate.init") super.init() } func application( _ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { lifecycleLog.info("willFinishLaunching") return true } func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { lifecycleLog.info("didFinishLaunching") return true } func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions ) -> UISceneConfiguration { lifecycleLog.info("configurationForConnecting") return UISceneConfiguration( name: "Default Configuration", sessionRole: connectingSceneSession.role ) } } The scene delegate owns the lifecycle of one UI instance: final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { lifecycleLog.info("scene.willConnect") log(connectionOptions) } func sceneWillEnterForeground(_ scene: UIScene) { lifecycleLog.info("scene.willEnterForeground") } func sceneDidBecomeActive(_ scene: UIScene) { lifecycleLog.info("scene.didBecomeActive") } func sceneWillResignActive(_ scene: UIScene) { lifecycleLog.info("scene.willResignActive") } func sceneDidEnterBackground(_ scene: UIScene) { lifecycleLog.info("scene.didEnterBackground") } private func log(_ options: UIScene.ConnectionOptions) { lifecycleLog.info( "Connection options: URLs=\(options.urlContexts.count), activities=\(options.userActivities.count), shortcut=\(options.shortcutItem != nil), notification=\(options.notificationResponse != nil)" ) } } The launch methods and scene connection method run on the main actor. Expensive synchronous work in this path delays UI construction. We will profile that work in Part 3 and redesign it in Part 4. For now, the important point is simply where that work can execute. A note about launch options In applications that use scenes, do not expect the app delegate's `launchOptions` dictionary to describe why the UI is being created. Apple explicitly directs scene-based apps to inspect `UIScene.ConnectionOptions` in `scene(_:willConnectTo:options:)` instead. Legacy applications without scenes still receive launch reasons through `UIApplication.LaunchOptionsKey` in the app delegate. Mixing examples from the two lifecycle models is a common source of duplicate or missing routing. What executes during a resume? Suppose the application entered the background, remained in memory, and was suspended. The person now taps its icon. For an existing scene, the relevant path is approximately: Suspended process resumes ↓ sceneWillEnterForeground(_:) ↓ Application refreshes foreground-only state ↓ sceneDidBecomeActive(_:) ↓ Scene accepts user events The launch callbacks do not execute again: // These are not called again during a normal resume: // // AppDelegate.init // application(_:willFinishLaunchingWithOptions:) // application(_:didFinishLaunchingWithOptions:) // scene(_:willConnectTo:options:) for the existing scene `sceneDidBecomeActive(_:)` means that the scene is active and responding to user events. It does not prove that application-specific data is fresh or that an asynchronous destination is ready. A resume metric may therefore need a later product milestone, such as `timelineLoaded` or `cameraReady`. Launch type and entry point are independent dimensions Cold, warm, and resume describe the state of the process. They do not describe *why* the application opened. The entry point might be: the app icon; a custom URL scheme; a Universal Link; a notification; a Home Screen quick action; Spotlight, Handoff, or another `NSUserActivity`; a document or file URL; a widget, Live Activity, App Intent, or other system surface that ultimately supplies a URL or activity. These dimensions form a matrix. A product deep link can create a cold process, create a warm process, activate an existing scene, or request a new scene in an already running process. That is why a routing implementation needs at least two delivery paths: Connection-time delivery when UIKit is creating a scene. Runtime delivery when UIKit already has a scene to receive the request. The following table summarizes the scene-based UIKit APIs: Entry point New scene Existing scene or running app App icon Empty or default connection context Foreground/activation callbacks Custom URL scheme `connectionOptions.urlContexts` `scene(_:openURLContexts:)` Universal Link `connectionOptions.userActivities` `scene(_:continue:)` Notification interaction `connectionOptions.notificationResponse` when connecting for the response `UNUserNotificationCenterDelegate` response callback Home Screen quick action `connectionOptions.shortcutItem` `windowScene(_:performActionFor:completionHandler:)` Spotlight or Handoff `connectionOptions.userActivities` `scene(_:continue:)` Document or file URL `connectionOptions.urlContexts` `scene(_:openURLContexts:)` The table shows where to look, not a universal total ordering between every lifecycle callback. Multi-window configuration, scene selection, notification targeting, and state restoration can influence the exact sequence. Instrument the application you actually ship. Opening from the app icon An ordinary icon launch has no external navigation intent. During a new scene connection, the connection options are normally empty: func resolveDefaultLaunch( from options: UIScene.ConnectionOptions ) -> Bool { options.urlContexts.isEmpty && options.userActivities.isEmpty && options.shortcutItem == nil && options.notificationResponse == nil } If the process and scene already exist, tapping the icon brings the scene to the foreground. There is no new call to `didFinishLaunching` and no new connection context for the existing scene. The destination for an icon launch is usually the default or restored UI. Its performance endpoint might be “home content visible and interactive.” That endpoint is different from the endpoint for a product deep link or a scanning quick action. Opening through a custom URL scheme Consider a URL such as: yourapp://screen/screen_example If UIKit creates a scene to open it, the URL is available in the scene connection options: func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { for context in connectionOptions.urlContexts { launchIntentQueue.enqueue(.url(context.url)) } buildRootInterface() launchIntentQueue.beginProcessing() } If a scene already exists, UIKit uses the URL-opening callback: func scene( _ scene: UIScene, openURLContexts URLContexts: Set ) { for context in URLContexts { router.handle(.url(context.url), in: scene) } } This distinction prevents a common bug: implementing only `scene(_:openURLContexts:)` and discovering that URLs work while the app is running but fail when the URL creates a new scene. Notice that `urlContexts` is a `Set`. Production code should not silently assume that `.first` defines a meaningful priority if multiple contexts are ever delivered. Validate supported URLs and make any selection rule explicit. Opening through a Universal Link A Universal Link arrives as an `NSUserActivity`, normally with the activity type `NSUserActivityTypeBrowsingWeb` and the destination in `webpageURL`. During scene creation, inspect `connectionOptions.userActivities`: func resolveUniversalLinks( from options: UIScene.ConnectionOptions ) -> [URL] { options.userActivities.compactMap { activity in guard activity.activityType == NSUserActivityTypeBrowsingWeb else { return nil } return activity.webpageURL } } For an existing scene, handle the activity in `scene(_:continue:)`: func scene( _ scene: UIScene, continue userActivity: NSUserActivity ) { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return } router.handle(.universalLink(url), in: scene) } Connection-time delivery creates another architectural constraint: receiving an intent and executing it are separate actions. When `willConnectTo` runs, the root controller, dependency graph, authentication state, or restored navigation stack may not be ready. Queueing a typed intent is safer than immediately pushing a view controller from the lifecycle callback. Opening from a notification There are three events that teams often mix together: A notification is delivered. The user interacts with the notification or one of its actions. That interaction activates or creates application UI. For navigation performance, the second and third events are normally the relevant ones. When UIKit connects a scene to process a notification response, it makes the response available in `connectionOptions.notificationResponse`: func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { if let response = connectionOptions.notificationResponse { let content = response.notification.request.content launchIntentQueue.enqueue( .notification( userInfo: content.userInfo, actionIdentifier: response.actionIdentifier ) ) } buildRootInterface() launchIntentQueue.beginProcessing() } Notification interactions are also delivered through `UNUserNotificationCenterDelegate`. A modern implementation may use the async callback: import UserNotifications extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse ) async { await notificationRouter.handle(response) } } `UNNotificationResponse` contains both the notification and the selected action identifier. The default action indicates that the user opened the app from the notification interface; a custom identifier may represent a product action such as “Reply,” “Accept,” or “View order.” In scene-based applications, centralize and deduplicate notification intents rather than letting both scene connection and notification delegate code navigate independently. The lifecycle layer should translate system objects into an application-owned intent; one router should decide when and where to execute it. A background notification is a separate scenario. It may launch or wake the process without presenting UI, and it should not be mixed into a foreground time-to-interactive distribution. Opening through a Home Screen quick action Quick actions are another example of connection-time and runtime delivery. During scene creation: if let shortcutItem = connectionOptions.shortcutItem { launchIntentQueue.enqueue(.shortcut(shortcutItem)) } When the application already has an appropriate window scene, implement the scene delegate callback: func windowScene( _ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void ) { Task { @MainActor in let succeeded = await router.handle( .shortcut(shortcutItem), in: windowScene ) completionHandler(succeeded) } } The old `UIApplicationDelegate` quick-action callback is deprecated for scene-based apps. Apple directs scene-based applications to `UIWindowSceneDelegate` instead. A quick action can also change the definition of “startup finished.” For a normal icon launch, the destination might be the home screen. For a “Scan QR code” shortcut, the meaningful endpoint may be the moment the camera preview accepts input. Measuring both paths to the home screen would hide the actual quick-action experience. Spotlight, Handoff, Siri, and other user activities Several system integrations use `NSUserActivity`. At connection time, activities are available in `connectionOptions.userActivities`. For an existing scene, UIKit calls `scene(_:continue:)`. import CoreSpotlight func scene( _ scene: UIScene, continue userActivity: NSUserActivity ) { switch userActivity.activityType { case NSUserActivityTypeBrowsingWeb: guard let url = userActivity.webpageURL else { return } router.handle(.universalLink(url), in: scene) case CSSearchableItemActionType: router.handle(.spotlight(userActivity), in: scene) default: router.handle(.userActivity(userActivity), in: scene) } } The same two-stage design applies: Translate the system object into a typed application intent. Execute that intent only when the correct scene and its dependencies are ready. Unify entry points before routing Lifecycle delegates are delivery mechanisms, not navigation architecture. Without a shared abstraction, large applications often accumulate several competing routers: a URL parser in `SceneDelegate`; push navigation in `AppDelegate`; quick-action logic beside the root controller; Handoff handling inside an unrelated feature module; separate cold-start and runtime implementations of the same route. A small application-owned model gives every entry point the same destination pipeline: enum AppLaunchIntent { case defaultLaunch case url(URL) case universalLink(URL) case notification( userInfo: [AnyHashable: Any], actionIdentifier: String ) case shortcut(UIApplicationShortcutItem) case spotlight(NSUserActivity) case userActivity(NSUserActivity) } The connection-time resolver can then collect all relevant intents without navigating: struct LaunchIntentResolver { func resolve( _ options: UIScene.ConnectionOptions ) -> [AppLaunchIntent] { var intents: [AppLaunchIntent] = [] if let response = options.notificationResponse { intents.append( .notification( userInfo: response.notification.request.content.userInfo, actionIdentifier: response.actionIdentifier ) ) } if let shortcut = options.shortcutItem { intents.append(.shortcut(shortcut)) } intents += options.urlContexts.map { .url($0.url) } intents += options.userActivities.compactMap { activity in if activity.activityType == NSUserActivityTypeBrowsingWeb, let url = activity.webpageURL { return .universalLink(url) } return .userActivity(activity) } return intents.isEmpty ? [.defaultLaunch] : intents } } If the product supports conflicting simultaneous intents, define a product-level priority explicitly. Do not let the order of unrelated `if` statements silently become navigation policy. This separation will become more important in Part 4, where we build a startup coordinator and dependency graph. It already helps measurement: every intent can carry its own start, destination-ready, and interactive milestones. Where should startup measurement begin? A common first attempt looks like this: func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let startedAt = Date() // Initialize the app... return true } This can measure code that executes after the marker, but it cannot measure total launch time. By the time `didFinishLaunching` begins, the system has already created the process, loaded the executable and dependencies, performed runtime initialization, entered `main`, initialized UIKit, and created the app delegate. Starting a timer in `SceneDelegate` excludes even more work. Use two layers of measurement instead: System launch metrics Use system-provided tools for the end-to-end process-launch interval. Depending on the purpose, those tools include: Xcode Organizer launch metrics; the App Launch and Time Profiler instruments; `XCTApplicationLaunchMetric` in performance tests; production launch metrics from MetricKit. Apple's current guidance describes the Organizer launch interval as the time between the user tapping the icon and the first application screen being drawn after the static launch screen. It also recommends comparing median and 90th-percentile values rather than relying on one observation. We will implement this measurement setup in Part 2. Application milestones Use monotonic markers or signposts to break application-owned work into meaningful segments: app delegate entered; scene connection began; root UI constructed; incoming intent accepted; destination content visible; -destination interactive. A minimal timeline recorder is enough to explore callback order: import OSLog @MainActor final class LaunchTimeline { static let shared = LaunchTimeline() private let logger = Logger( subsystem: Bundle.main.bundleIdentifier ?? "ExampleApp", category: "LaunchTimeline" ) private var firstObservedUptime: TimeInterval? func mark(_ event: String) { let now = ProcessInfo.processInfo.systemUptime let origin = firstObservedUptime ?? now firstObservedUptime = origin logger.info( "\(event, privacy: .public) +\(now - origin, format: .fixed(precision: 3))s" ) } } Use it from each callback: func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { LaunchTimeline.shared.mark("app.didFinishLaunching") return true } func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { LaunchTimeline.shared.mark("scene.willConnect") } func sceneDidBecomeActive(_ scene: UIScene) { LaunchTimeline.shared.mark("scene.didBecomeActive") } This recorder intentionally starts at the first observed application marker. It reveals relative callback timing but does not include pre-main work and must not be reported as total launch time. Where should startup measurement end? There is no single universally correct endpoint. At least three milestones matter: Time to First Draw The first application frame replaces the static launch screen. This is a system-observable rendering milestone. Time to Meaningful Content The user sees content that represents the requested destination rather than an empty container, generic placeholder, or unrelated default screen. Time to Interactive The requested experience is able to accept its primary user action without main-thread blocking or a required initialization gate. For example: Entry point Possible meaningful endpoint App icon Home content visible and primary controls enabled Product Universal Link Requested product visible and actionable Notification Notification destination rendered with current state “Scan” quick action Camera preview active and accepting input Document URL Requested document rendered or an explicit recoverable error shown This produces an entry-point-specific metric: Time from user intent to destination interactive. That metric often reveals problems hidden by an excellent default icon launch. An app can render its home screen quickly and still take several seconds to process the route that the user actually requested. Do not use `viewDidAppear` as proof that a frame was displayed or the screen is interactive. It is a lifecycle callback, not a complete user-experience metric. Combine system rendering metrics with explicit product milestones. What changes in SwiftUI? SwiftUI changes the APIs visible to application code, but not the underlying questions. An app built with the SwiftUI lifecycle may receive external events through environment actions and view modifiers: @main struct ExampleApp: App { @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { RootView() .onOpenURL { url in router.handle(.url(url)) } .onContinueUserActivity( NSUserActivityTypeBrowsingWeb ) { activity in guard let url = activity.webpageURL else { return } router.handle(.universalLink(url)) } } .onChange(of: scenePhase) { _, newPhase in lifecycleTimeline.mark("scenePhase.\(String(describing: newPhase))") } } } You may also bridge process-level UIKit responsibilities with `@UIApplicationDelegateAdaptor`. What should not change is the architecture: distinguish process launch from resume; distinguish scene creation from scene activation; translate each external event into a typed intent; avoid navigating before the destination dependencies are ready; measure the requested user journey, not merely a convenient callback. The construction of `App.body` is not a reliable replacement for a system launch start timestamp, just as `didFinishLaunching` is not. Build a launch matrix for your own application Before opening Instruments, add lifecycle logging and perform a small experiment on a physical device. Test at least these scenarios: Restart the device, unlock it, and open the app from the icon. Force-quit the app and immediately open it again. Send the app to the background and return from the app switcher. Open a custom URL while the process does not exist. Open the same URL while a scene already exists. Open a Universal Link in both states. Tap a notification in both states. Use every Home Screen quick action. If supported, open a second window on iPadOS. Repeat the tests with an authenticated and unauthenticated user. Record the observed results: Scenario New process New scene Launch callbacks Entry payload location Product-ready endpoint Icon after reboot Yes Yes App + scene None Home interactive Icon after force quit Yes Yes App + scene None Home interactive Resume from background No No Foreground/active only None Existing screen interactive URL with no process Yes Yes App + scene Connection options URL destination interactive URL with existing scene No No URL callback `openURLContexts` URL destination interactive Quick action with no process Yes Yes App + scene Connection options Action destination interactive Quick action with existing scene No Usually no Quick-action callback Shortcut callback Action destination interactive Treat this table as a template, not expected test output to copy. Your application's scene configuration and product rules are part of the result. This experiment usually exposes at least one of the following: cold-start routes that are never processed; duplicate navigation from two delegates; intents executed before authentication restoration completes; lifecycle callbacks incorrectly treated as user-ready milestones; default-launch metrics applied to routes with very different work; resume work incorrectly placed in launch-only methods. Conclusion An iOS application does not have one startup path. It has a matrix of process states, scene states, entry points, and user-visible destinations. Cold and warm launches both create a new process and run the launch lifecycle. A resume reuses an existing process and normally an existing scene. A new scene can be created without a new process. Prewarming can shift some work before the user's explicit action. Entry points form a separate dimension. A URL, Universal Link, notification, quick action, or user activity has one delivery path when UIKit is connecting a scene and another when a suitable scene already exists. Both paths should feed the same application-owned intent and routing pipeline. These distinctions change how startup performance should be measured. A timer started in `didFinishLaunching` cannot include the complete process launch. A metric that stops at the first frame may not tell us when the requested destination becomes useful. For many products, the most honest metric is the time from the user's entry intent to that destination becoming interactive. In Part 2: Measuring App Launch Time Correctly, we will turn this lifecycle model into a reproducible benchmark using Instruments, XCTest, signposts, physical devices, and production percentiles. We will also examine why repeatedly launching an app on a developer's phone often produces a stable measurement of the wrong scenario.
iOS Startup Performance in Practice, Part 1: Understanding Launch Types and Entry Points
Full Article
Original Source
Read the full article at Hackernoon →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.