--- source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/app-lifecycle-state-machine.mdc confidence: 0.9 namespace: work last_synced: 2026-04-28 alwaysApply: false --- # App Lifecycle State Machine Architecture ## Overview The DuckDuckGo browser has moved away from traditional AppDelegate-based lifecycle handling to a **state machine architecture**. While AppDelegate still exists, it has been significantly thinned out and now delegates responsibility to a structured state machine. This approach ensures that lifecycle handling is **predictable, organized, and easy to maintain**. ## Architecture Components ### Three Core States The architecture revolves around a state machine with three major states: #### 1. **Launching** (Transient State) - **Associated with**: `application(_:didFinishLaunchingWithOptions:)` - **File**: `Launching.swift` - **Purpose**: App's initial setup and dependency configuration - **Responsibilities**: - Initialize all services and objects - Configure dependencies - Prepare UI components - Create `MainViewController` and set as `rootViewController` #### 2. **Foreground** (Permanent State) - **Associated with**: `applicationDidBecomeActive(_:)` - **File**: `Foreground.swift` - **Purpose**: App is fully interactive and user can engage with UI - **Responsibilities**: - Resume suspended work - Handle user interactions - Manage active UI state #### 3. **Background** (Permanent State) - **Associated with**: `applicationDidEnterBackground(_:)` - **File**: `Background.swift` - **Purpose**: App is not active and UI is not visible - **Responsibilities**: - Suspend ongoing work that doesn't need background execution - Prepare for potential termination - Handle background tasks ## State Machine Methods ### Core Transition Methods All states implement specific methods for handling transitions: #### `onTransition()` - **When**: Called whenever the app enters that state from another state - **Purpose**: Setup or cleanup during state transitions - **Available in**: Foreground, Background #### `willLeave()` - **When**: Called before transitioning away from current state - **Purpose**: Prepare for potential state change - **Note**: Transition may be cancelled, in which case `didReturn()` is called - **Available in**: Foreground, Background #### `didReturn()` - **When**: Called after successful transition to destination state OR when transition is cancelled - **Purpose**: Finalize state entry or handle cancelled transition - **Available in**: Foreground, Background ## Common Lifecycle Scenarios ### Cold App Start ```swift // Flow: Launching → Foreground 1. Launching.init() // Initial setup 2. Foreground.onTransition() // Enter foreground 3. Foreground.didReturn() // Finalize foreground entry ``` ### App Backgrounding ```swift // Flow: Foreground → Background 1. Foreground.willLeave() // Prepare to leave foreground 2. Background.onTransition() // Enter background 3. Background.didReturn() // Finalize background entry ``` ### App Foregrounding ```swift // Flow: Background → Foreground 1. Background.willLeave() // Prepare to leave background 2. Foreground.onTransition() // Enter foreground 3. Foreground.didReturn() // Finalize foreground entry ``` ### Interrupted Foreground (Alert/App Switcher) ```swift // User receives alert but dismisses it 1. Foreground.willLeave() // Attempt to leave 2. Foreground.didReturn() // Cancelled - stay in foreground // User opens App Switcher 1. Foreground.willLeave() // Attempt to leave // Two possible outcomes: // A. User returns directly: 2. Foreground.didReturn() // Return to foreground // B. User switches to another app: 2. Background.onTransition() // Actually transition to background 3. Background.didReturn() // Finalize background entry ``` ## Special iOS 18+ Scenarios ### Face ID Authentication on Cold Start #### Successful Authentication ```swift 1. Launching.init() 2. Foreground.onTransition() 3. Foreground.didReturn() ``` #### Failed Authentication ```swift 1. Launching.init() 2. Background.onTransition() // Goes to background on auth failure 3. Background.didReturn() ``` ### DuckDuckGo Face ID Lock #### Cold Start with DDG Face ID ```swift 1. Launching.init() 2. Foreground.onTransition() 3. Foreground.didReturn() 4. Foreground.willLeave() // DDG auth triggers 5. Foreground.didReturn() // User passes auth ``` ### Critical Setup Failure ```swift 1. Launching.init() throws // Setup fails (e.g., disk space) 2. Terminating.init() // App terminates ``` ## Code Placement Patterns ### ⚙️ One-time Setup → `AppConfiguration` **Location**: Inside `Launching.swift` For setup that happens once and doesn't need ongoing lifecycle management: ```swift class AppConfiguration { func start() { // Basic setup that doesn't require dependencies setupGlobalUserAgent() configureLogging() } func finalize() { // Setup that requires access to services or MainCoordinator configureWithDependencies() } } ``` **Use Cases**: - Setting global user agents - Initial configuration - One-time system setup ### 🔄 Lifecycle-Reactive Logic → `Service` For code that needs to react to app lifecycle events: ```swift class MyLifecycleService { func resumeWork() { // Called from Foreground.onTransition() or didReturn() } func suspendWork() { // Called from Background.onTransition() or Foreground.willLeave() } } // In Launching.swift let myService = MyLifecycleService() services.myService = myService // Store in services for lifecycle access ``` **Service Patterns**: - **Initialize**: In `Launching.init()` - **Resume work**: In `Foreground` methods - **Suspend work**: In `Background` methods - **Assign to services**: Make available to other states **Use Cases**: - Network managers - Timer services - Data synchronization - Background task management ### 🖼️ UI-Related Logic → `MainCoordinator` **Location**: MainCoordinator initialization and management For logic that involves creating or modifying the main view: ```swift class MainCoordinator { func setupMainViewController() { // UI setup and configuration } func handleDeepLink(_ url: URL) { // Navigation and UI state changes } } ``` **Use Cases**: - View controller creation - Navigation management - UI state configuration - Deep link handling ## Practical Examples ### 📊 Example 1: Pixel Analytics Service **Requirement**: Send "Hello" pixel on foreground, "Goodbye" pixel on background ```swift // 1. Create Service class PixelService { func sendHelloPixel() { // Send hello pixel } func sendGoodbyePixel() { // Send goodbye pixel } } // 2. Initialize in Launching class Launching { func init() { let pixelService = PixelService() services.pixelService = pixelService } } // 3. Use in Foreground class Foreground { func onTransition() { services.pixelService.sendHelloPixel() } } // 4. Use in Background class Background { func onTransition() { services.pixelService.sendGoodbyePixel() } } ``` ### ⏱️ Example 2: Session Timer Service **Requirement**: Track session time, pause on interruptions, resume on return ```swift class SessionTimeService { private var timer: Timer? func startTimer() { // Start session timing } func pauseTimer() { // Pause session timing } func resumeTimer() { // Resume session timing } } // Launching class Launching { func init() { let sessionService = SessionTimeService() services.sessionService = sessionService } } // Foreground - Handle interruptions class Foreground { func didReturn() { // Start/resume timer when entering or returning to foreground services.sessionService.resumeTimer() } func willLeave() { // Pause timer when potentially leaving foreground services.sessionService.pauseTimer() } } // Background class Background { func onTransition() { // Timer already paused by Foreground.willLeave() } } ``` ### 🧹 Example 3: Auto-Clear Data Service **Requirement**: Clear data immediately on app wake to avoid UI glitches ```swift class AutoClearService { func startDataClearing() async { // Clear user data } func waitForCompletion() async { // Wait for clearing to complete } } // Launching - Start clearing immediately class Launching { func init() { let autoClearService = AutoClearService() services.autoClearService = autoClearService // Start clearing immediately on cold start Task { await autoClearService.startDataClearing() } } } // Foreground - Wait for completion before proceeding class Foreground { func onTransition() async { // Wait for data clearing before loading URLs or handling deep links await services.autoClearService.waitForCompletion() handlePendingDeepLinks() } } // Background - Start clearing before transitioning to foreground class Background { func willLeave() { // Start clearing early to be ready for foreground transition Task { await services.autoClearService.startDataClearing() } } func didReturn() { // If transition was cancelled, clearing is still beneficial // No action needed as clearing is irreversible } } ``` ## State Context and Services ### Service Management ```swift // Services are stored in StateContext for cross-state access class StateContext { var pixelService: PixelService! var sessionService: SessionTimeService! var autoClearService: AutoClearService! // ... other services } // Access pattern in states class Foreground { func onTransition() { services.pixelService.sendHelloPixel() services.sessionService.resumeTimer() } } ``` ### Service Lifecycle Best Practices ```swift // ✅ CORRECT: Service with proper lifecycle management class MyService { private var isActive = false func activate() { guard !isActive else { return } isActive = true startWork() } func deactivate() { guard isActive else { return } isActive = false stopWork() } private func startWork() { // Begin service operations } private func stopWork() { // Clean up service operations } } // Usage in states class Foreground { func didReturn() { services.myService.activate() } func willLeave() { services.myService.deactivate() } } ``` ## Decision Tree: Where Should My Code Go? ``` 📋 What type of code are you adding? ├── 🔧 One-time setup that doesn't need lifecycle management? │ └── ➡️ AppConfiguration (in Launching.swift) │ ├── start() for basic setup │ └── finalize() for dependency-requiring setup │ ├── 🔄 Logic that reacts to app state changes? │ └── ➡️ Create a Service │ ├── Initialize in Launching.init() │ ├── Store in services for cross-state access │ ├── Resume work in Foreground methods │ └── Suspend work in Background methods │ ├── 🖼️ UI setup or view management? │ └── ➡️ MainCoordinator │ ├── View controller creation │ ├── Navigation setup │ └── Deep link handling │ └── 🤔 Something else? └── ➡️ Let's discuss through tech design ``` ## Best Practices ### ✅ DO ```swift // Store services for cross-state access services.myService = MyService() // Use proper lifecycle methods func didReturn() { resumeWork() } func willLeave() { pauseWork() } // Handle state transitions gracefully func onTransition() { await waitForCriticalWork() proceedWithStateLogic() } ``` ### ❌ DON'T ```swift // Don't bypass the state machine AppDelegate.shared.doSomething() // ❌ // Don't create services without storing them let service = MyService() // ❌ Will be deallocated // Don't ignore willLeave/didReturn patterns func onTransition() { // Only using onTransition misses important interrupt scenarios } // Don't block UI with long operations func onTransition() { performLongRunningTask() // ❌ Should be async } ``` ### 🔒 Memory Management ```swift // Services are retained by StateContext class StateContext { var services: [String: AnyObject] = [:] func addService(_ service: T, for key: String) { services[key] = service } } // Clean up resources in state transitions class MyService { func cleanup() { // Release resources, cancel operations } } ``` ## Debugging and Monitoring ### State Transition Logging ```swift class Foreground { func onTransition() { Logger.lifecycle.info("Entering Foreground state") // State logic } func willLeave() { Logger.lifecycle.info("Will leave Foreground state") // Cleanup logic } func didReturn() { Logger.lifecycle.info("Returned to Foreground state") // Resume logic } } ``` ### Performance Monitoring ```swift class Launching { func init() { let startTime = CFAbsoluteTimeGetCurrent() // Initialization logic let duration = CFAbsoluteTimeGetCurrent() - startTime Logger.performance.info("Launching completed in \(duration)s") } } ``` --- This state machine architecture provides a robust, maintainable approach to app lifecycle management that scales with the complexity of the DuckDuckGo browser while maintaining clear separation of concerns.