Irbisa · Cheatsheet

App Lifecycle, Background & Push

Cold launch, scene phases, background modes, BGTaskScheduler, APNs, silent push, universal links

Middle Developer · 23 questions

  1. 01

    What states can an iOS app be in (not running, inactive, active, background, suspended), and what moves it from one to the next?

    An app is not running, inactive (on screen but not receiving events), active (on screen and receiving events), in the background (off screen, still running code) or suspended (in memory, running nothing).

    final class SceneDelegate: UIResponder, UIWindowSceneDelegate {  var window: UIWindow?   // background -> inactive: about to return to the screen  func sceneWillEnterForeground(_ scene: UIScene) { feed.refreshIfStale() }…
  2. 02

    What is the difference between AppDelegate and SceneDelegate, and why did iOS 13 move the UI lifecycle into scenes?

    AppDelegate handles the process: launch, push tokens, background URLSession events, choosing a scene configuration.

    @mainfinal class AppDelegate: UIResponder, UIApplicationDelegate {  // Once per process, with or without UI  func application(_ application: UIApplication,                   didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {    Analytics.configure()…
  3. 03

    How do you observe the app lifecycle in SwiftUI with scenePhase, and when do you still need @UIApplicationDelegateAdaptor?

    Read @Environment(\.scenePhase) and react in .onChange(of:): .active, .inactive and .background match the UIKit scene states, and @UIApplicationDelegateAdaptor brings back an AppDelegate for the callbacks SwiftUI has no modifier for.

    @mainstruct ShopApp: App {  @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate  @Environment(\.scenePhase) private var scenePhase  @State private var store = CartStore()…
  4. 04

    Is applicationWillTerminate guaranteed to run, and where should you save state instead?

    No. It runs only if the app is terminated while still executing, and most apps die while suspended, when the system kills them with no callback at all.

  5. 05

    What is the difference between local and remote notifications, and how do you ask the user for permission?

    A local notification is scheduled by the app on the device with a trigger; a remote one is sent by your server through APNs to a device token.

    import UIKitimport UserNotifications @MainActorfunc enableReminders() async throws {  let center = UNUserNotificationCenter.current()…
  6. 06

    Session counts doubled after this shipped. Why, and which callback should start a session?

    final class SceneDelegate: UIResponder, UIWindowSceneDelegate {  var window: UIWindow?   func sceneDidBecomeActive(_ scene: UIScene) {    Analytics.log("session_start")  }   func sceneDidEnterBackground(_ scene: UIScene) {    Analytics.log("session_end")  }}

    sceneDidBecomeActive fires again after every interruption that only makes the app inactive (a Face ID prompt, Control Center, a system alert), so it logs extra starts with no matching end.

  7. 07

    What happens between the user tapping the icon and application(_:didFinishLaunchingWithOptions:)?

    The system creates the process, dyld loads and links every dynamic library, the Objective-C runtime and static initialisers run, and then main calls UIApplicationMain, which creates UIApplication and your app delegate and only then calls willFinishLaunching and didFinishLaunching.

  8. 08

    How does iOS limit background work, and what legitimate ways are there to run code after the user leaves the app?

    After the user leaves, an app gets a few seconds in the background and is then suspended.

    <!-- Info.plist: declare only what the app genuinely does --><key>UIBackgroundModes</key><array>  <!-- lets the system wake you for BGAppRefreshTask -->  <string>fetch</string>  <!-- lets the system wake you for BGProcessingTask -->…
  9. 09

    This code keeps an upload alive after the user leaves the app. What is wrong with it?

    func sceneDidEnterBackground(_ scene: UIScene) {  let app = UIApplication.shared  let id = app.beginBackgroundTask(withName: "upload")   uploader.upload(draft) { result in    if case .success = result {      app.endBackgroundTask(id)    }  }}

    There is no expiration handler and the task is ended only on success, so a slow or failed upload leaves the identifier open; when the budget runs out, the watchdog kills the app with 0x8badf00d, which users see as a crash on switching away.

  10. 10

    Find the bug: a user checks another app for a few minutes mid-workout, and on return this timer is minutes behind.

    struct WorkoutTimerView: View {  @State private var elapsed = 0  private let ticker = Timer.publish(every: 1, on: .main, in: .common).autoconnect()   var body: some View {    Text(Duration.seconds(elapsed).formatted(.time(pattern: .minuteSecond)))      .monospacedDigit()      .onReceive(ticker) { _ in        elapsed += 1      }  }}

    The view counts ticks instead of measuring time.

  11. 11

    How do you schedule work with BGTaskScheduler, and why might a registered task never run?

    Register a handler for the identifier before didFinishLaunching returns, list the identifier in Info.plist, then submit a BGAppRefreshTaskRequest or BGProcessingTaskRequest.

    // Register before didFinishLaunching returnsfunc application(_ application: UIApplication,                 didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.nightly-sync",                                  using: nil) { task in    handleSync(task as! BGProcessingTask)…
  12. 12

    How do you test a BGTaskScheduler task without waiting for the system to run it?

    On a device, background the app after it has submitted the request, pause in Xcode and call the private _simulateLaunchForTaskWithIdentifier: from LLDB; the registered handler runs immediately.

    # 1. Run on a device from Xcode, let the app call submit(),#    then send it to the background.# 2. Press Pause in Xcode and enter in the LLDB console: e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.app.nightly-sync"]…
  13. 13

    How do push notifications work end to end: APNs, the device token and the payload?

    The app asks APNs for a device token and sends it to your server; the server posts a JSON payload for that token to APNs over HTTP/2, authenticated with a .p8 key or a certificate, and APNs delivers it so iOS can show it or wake the app.

    func application(_ application: UIApplication,                 didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  // Every launch: the token may have changed since last time.  application.registerForRemoteNotifications()  return true}…
  14. 14

    What does a silent push need to wake your app, and why might it still not arrive?

    The payload needs "content-available": 1 and nothing visible, the request needs apns-push-type: background and apns-priority: 5, and the app needs the remote-notification background mode.

    // What the server sends://   apns-push-type: background   <- alert here means no background wake//   apns-priority: 5             <- 10 is not allowed for background pushes//   apns-topic: com.example.app//   { "aps": { "content-available": 1 }, "reason": "feed" }…
  15. 15

    Find the bug in this silent push handler: the push arrives, yet the inbox is still stale when the user opens the app.

    func application(_ application: UIApplication,                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {  guard userInfo["reason"] as? String == "inbox" else {    completionHandler(.noData)    return  }  Task {    try await inbox.sync()  }  completionHandler(.newData)}

    completionHandler(.newData) runs right after the Task is created, before the sync has done anything.

  16. 16

    How do you show or hide a notification that arrives while the app is open, and how do taps and action buttons reach userNotificationCenter(_:didReceive:)?

    Implement userNotificationCenter(_:willPresent:) to decide whether a foreground notification is shown, and register a UNNotificationCategory with actions at launch; the payload's category key picks the buttons, and userNotificationCenter(_:didReceive:) handles taps and actions.

    func application(_ application: UIApplication,                 didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  let reply = UNTextInputNotificationAction(identifier: "REPLY", title: "Reply", options: [],                                            textInputButtonTitle: "Send", textInputPlaceholder: "Message")  let message = UNNotificationCategory(identifier: "MESSAGE",   // "category": "MESSAGE" in aps                                       actions: [reply], intentIdentifiers: [])…
  17. 17

    What does provisional notification authorization give you, and why can an .authorized user still see nothing?

    Adding .provisional to requestAuthorization grants permission without any prompt, but notifications go quietly to Notification Center, with no banner or sound, until the user taps Keep.

    let center = UNUserNotificationCenter.current() func requestProvisional() async throws {  // Day one: no prompt. Delivered quietly to Notification Center.  let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional])  // granted is true immediately; the user saw nothing.…
  18. 18

    What is the difference between universal links and custom URL schemes, and what does a universal link need to open your app?

    A custom scheme like myapp:// is unverified (any app can claim it and it fails when the app is not installed), while a universal link is an ordinary https URL that opens the app only after iOS verifies the domain, and opens the website otherwise.

    {  "applinks": {    "details": [      {        "appIDs": ["ABCDE12345.com.example.app"],        "components": […
  19. 19

    Universal links open the right screen while the app is running, but after a cold start they land on the home screen. What is missing here?

    final class SceneDelegate: UIResponder, UIWindowSceneDelegate {  var window: UIWindow?   func scene(_ scene: UIScene, willConnectTo session: UISceneSession,             options connectionOptions: UIScene.ConnectionOptions) {    guard let windowScene = scene as? UIWindowScene else { return }    window = UIWindow(windowScene: windowScene)    window?.rootViewController = RootViewController()    window?.makeKeyAndVisible()  }   func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {    guard let url = userActivity.webpageURL else { return }    router.open(Route(url) ?? .home)  }}

    On a cold launch the link does not come through scene(_:continue:): it arrives in connectionOptions.userActivities inside scene(_:willConnectTo:options:), and this code ignores it.

  20. 20

    How can iOS launch your app straight into the background, and what must didFinishLaunching avoid on that path?

    A silent push, a BGTaskScheduler task, a finished background URLSession, or a location or Bluetooth event can start the process with no UI.

    func application(_ application: UIApplication,                 didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  // Needed on every launch, visible or not.  registerBackgroundTasks()  UNUserNotificationCenter.current().delegate = self  return true…
  21. 21

    After the user swipes your app away in the app switcher, which background wake-ups still reach it?

    A force-quit tells iOS the user wants the app stopped: silent pushes, BGTaskScheduler tasks, background URLSession transfers and location relaunches stop until the user opens it again, while alert pushes and VoIP pushes still get through.

  22. 22

    What is app prewarming, and how does it distort your launch-time metrics?

    Since iOS 15 the system may start your process ahead of a predicted launch, run dyld and static initialisers, and then pause before UIApplicationMain.

    import UIKitimport os @MainActorenum LaunchClock {  // Set by the system only when this process was started by prewarming.…
  23. 23

    What is BGContinuedProcessingTask in iOS 26, and when would you use it instead of BGProcessingTask?

    It lets work the user explicitly started (an export, an upload, a large edit) keep running after they leave the app, with a system progress UI they can watch and cancel.

    import BackgroundTasks func startExport(_ video: Video) throws {  // Info.plist lists com.example.app.export.* in BGTaskSchedulerPermittedIdentifiers  let id = "com.example.app.export.\(video.id)"…