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.