Understanding the 5 iOS App States

iOS apps transition through five distinct states during their lifecycle: Not Running, Inactive (foreground but no touch), Active, Background (running code hidden), and Suspended (in memory, no cpu code). Understanding these states is crucial for building responsive, efficient iOS applications.

Here's what each state means:

  • Not Running: App hasn't been launched or was terminated by the system
  • Inactive: App is in foreground but not receiving events (transitioning states)
  • Active: App is running in foreground and receiving events—this is normal usage
  • Background: App is executing code but screen is hidden (limited time, typically ~30 seconds)
  • Suspended: App is in memory but not executing code—can be terminated if memory is needed

The system manages these transitions automatically, but your app must respond appropriately to lifecycle callbacks to ensure good user experience and prevent data loss.

Apps that don't handle lifecycle transitions properly feel sluggish and may lose user data—always save state at the right moments.

AppDelegate vs SceneDelegate: Understanding the Split

Since iOS 13, iPad allows multi-window. AppDelegate handles Process events (launch). SceneDelegate handles UI events (window focus). This separation enables apps to have multiple windows on iPad and provides better lifecycle management.

AppDelegate responsibilities:

  • App Launch: One-time setup when app starts
  • Process Events: System-level notifications
  • Push Notifications: Receiving remote notifications
  • Background Tasks: Registering background execution

SceneDelegate responsibilities:

  • Window Management: Creating and managing UI windows
  • Scene Lifecycle: Handling scene state transitions
  • User Interactions: Responding to scene-specific events
  • State Restoration: Restoring UI state per scene

For iPhone apps, you typically have one scene. For iPad apps with multi-window support, you can have multiple scenes. The SceneDelegate manages each scene independently, allowing users to work with multiple instances of your app simultaneously.

DidFinishLaunching: App Initialization

The code here runs once. Set up global configurations (Firebase, Analytics). Keep it fast; long startup times kill retention. This is your app's first impression—make it count.

What to do in DidFinishLaunching:

  • Initialize SDKs: Firebase, Analytics, Crashlytics
  • Configure Appearance: Navigation bar style, status bar style
  • Set Up Core Services: Database connections, API clients
  • Check First Launch: Onboarding flow, feature flags
  • Prepare Background Tasks: Register background fetch, processing

What NOT to do:

  • Heavy Network Calls: Blocking calls delay app launch
  • Large File Operations: Reading/writing large files slows startup
  • Synchronous Work: Always use async patterns
  • UI Creation: Defer UI setup to scene delegate

Target launch time: under 400ms for first screen. Use async initialization for non-critical setup. Show a splash screen while critical initialization completes.

WillResignActive: Preparing for Interruption

Called when a call comes in or user swipes up. Pause games, stop timers, calm down graphics. The app is about to be hidden. This is your last chance to pause operations gracefully before the user leaves.

When WillResignActive is called:

  • Phone Call: Incoming call interrupts your app
  • Control Center: User swipes up to access controls
  • Notification Center: User swipes down for notifications
  • Multi-Tasking: User switches to another app
  • Lock Screen: Device is being locked

What to do:

  • Pause Animations: Stop game loops, pause videos
  • Save Temporary State: Autosave drafts, game progress
  • Hide Sensitive Data: Blur sensitive screens, clear clipboard
  • Reduce Activity: Pause network requests, reduce CPU usage

This callback is brief—don't do heavy work here. The app will transition to Background shortly, where you have more time.

DidEnterBackground: Saving State and Releasing Resources

You have ~5 seconds to save data. Release large memory resources to avoid being killed by the OS. Request BackgroundTask if you need more time. This is critical—the system will terminate your app if you don't finish quickly.

Critical tasks to complete:

  • Save User Data: Persist any unsaved changes
  • Release Resources: Free large images, caches, memory
  • Invalidate Timers: Stop any running timers
  • Save App State: For state restoration

If you need more time:

  • Request BackgroundTask: Up to 30 seconds of background execution
  • Use BGTaskScheduler: For background app refresh
  • Defer Work: Schedule for next app launch

Memory management is crucial. Apps using too much memory are first to be terminated. Release everything you can—you can always reload when the user returns.

The 5-second rule isn't a suggestion—it's a requirement. Exceed it and your app may be killed immediately.

WillEnterForeground: Preparing for Return

Undo what you did in Background. Refresh data from server to ensure UI is up to date when the user sees it. This is your opportunity to prepare the app for active use again.

What to do:

  • Refresh Data: Check for server updates, sync local data
  • Resume Animations: Restart paused animations or timers
  • Update UI: Refresh views with latest data
  • Check Authentication: Verify user session is still valid
  • Handle Time Changes: If device time changed while away

Common scenarios:

  • Network Sync: Fetch updates that occurred while backgrounded
  • Location Updates: Refresh location-based content
  • Time-Sensitive Data: Update stock prices, news, etc.
  • Battery Check: Adjust features based on battery level

Don't block the UI thread. Use async patterns to load data. Show cached data immediately, then update with fresh data when available.

Termination: The Silent Exit

The OS kills apps silently to free memory. You usually don't get a notification. Rely on regular saving, not termination callbacks. This is the reality of iOS memory management.

When apps are terminated:

  • Low Memory: System needs memory for other apps
  • Background Limits: App exceeded background execution time
  • System Shutdown: Device is powering off
  • App Crashes: Unhandled exceptions or memory issues

Best practices:

  • Save Frequently: Don't wait for termination—save on every change
  • Use State Restoration: iOS can restore app state automatically
  • Implement Auto-Save: Save drafts and progress regularly
  • Handle Gracefully: Assume app may terminate anytime

Never rely on termination callbacks like applicationWillTerminate—they're not guaranteed to be called. Instead, implement a save-as-you-go strategy where critical data is persisted immediately.

Use NSCoder or Codable for state restoration. This allows iOS to restore your app's UI state when the user returns, making termination feel seamless.

Remember: the best user experience is when users don't notice termination at all. Your app should resume exactly where they left off, with all their data intact.