14 KiB
iOS Implementation Directive: App Launch Recovery & Missed Notification Detection
Author: Matthew Raymer
Date: 2025-12-08
Status: Active Implementation Directive - iOS Only
Version: 1.0.0
Last Synced With Plugin Version: v1.1.0
Purpose
This directive provides descriptive overview and integration guidance for iOS-specific recovery and missed notification detection:
- App Launch Recovery (cold/warm/terminated)
- Missed Notification Detection
- App Termination Detection
- Background Task Registration for Boot Recovery
⚠️ CRITICAL: This document is descriptive and integrative. The normative implementation instructions are in the Phase 1–3 directives below. If any code or behavior in this file conflicts with a Phase directive, the Phase directive wins.
Reference: See Plugin Requirements for requirements that Phase directives implement.
Reference: See Platform Capability Reference for iOS OS-level facts.
⚠️ IMPORTANT: For implementation, use the phase-specific directives (these are the canonical source of truth):
-
Phase 1: Cold Start Recovery - Minimal viable recovery
- Implements: Plugin Requirements §3.1.2 (iOS equivalent)
- Explicit acceptance criteria, rollback safety, data integrity checks
- Start here for fastest implementation
-
Phase 2: App Termination Detection & Recovery - Comprehensive termination handling
- Implements: iOS-specific app termination scenarios
- Prerequisite: Phase 1 complete
-
Phase 3: Background Task Registration & Boot Recovery - Background task enhancement
- Implements: BGTaskScheduler registration for boot recovery
- Prerequisites: Phase 1 and Phase 2 complete
See Also: Unified Alarm Directive for master coordination document.
1. Implementation Overview
1.1 What Needs to Be Implemented
| Feature | Status | Priority | Location |
|---|---|---|---|
| App Launch Recovery | ❌ Missing | High | DailyNotificationPlugin.swift - load() method |
| Missed Notification Detection | ⚠️ Partial | High | DailyNotificationPlugin.swift - new method |
| App Termination Detection | ❌ Missing | High | DailyNotificationPlugin.swift - recovery logic |
| Background Task Registration | ⚠️ Partial | Medium | AppDelegate.swift - BGTaskScheduler registration |
1.2 Implementation Strategy
Phase 1 – Cold start recovery only
- Missed notification detection + future notification verification
- No termination detection, no boot handling
- See Phase 1 directive for implementation
Phase 2 – App termination detection & full recovery
- Termination detection via UNUserNotificationCenter state comparison
- Comprehensive recovery of all schedules (notify + fetch)
- Past notifications marked as missed, future notifications rescheduled
- See Phase 2 directive for implementation
Phase 3 – Background task registration & boot recovery
- BGTaskScheduler registration for boot recovery
- Next occurrence rescheduled for repeating schedules
- See Phase 3 directive for implementation
2. iOS-Specific Considerations
2.1 Key Differences from Android
iOS Advantages:
- ✅ Notifications persist across app termination (OS-guaranteed)
- ✅ Notifications persist across device reboot (OS-guaranteed)
- ✅ No force stop equivalent (iOS doesn't have user-facing force stop)
iOS Challenges:
- ❌ App code does NOT run when notification fires (only if user taps)
- ❌ Background execution severely limited (BGTaskScheduler only)
- ❌ Cannot rely on background execution for recovery
- ❌ Must detect missed notifications on app launch
Platform Reference: See Platform Capability Reference §3 for complete iOS behavior matrix.
2.2 Recovery Scenario Mapping
Android → iOS Mapping:
| Android Scenario | iOS Equivalent | Detection Method |
|---|---|---|
COLD_START |
App Launch After Termination | Check if notifications exist vs DB state |
FORCE_STOP |
App Terminated by System | Check if notifications missing vs DB state |
BOOT |
Device Reboot | BGTaskScheduler registration (Phase 3) |
WARM_START |
App Resume (Foreground) | Check app state on resume |
Note: iOS doesn't have a user-facing "force stop" equivalent. System termination is detected by comparing UNUserNotificationCenter state with database state.
2.3 iOS APIs Used
Notification Management:
UNUserNotificationCenter.current()- Notification centerUNUserNotificationCenter.getPendingNotificationRequests()- Check scheduled notificationsUNUserNotificationCenter.add()- Schedule notifications
Background Tasks:
BGTaskScheduler.shared- Background task schedulerBGTaskScheduler.register()- Register background task handlersBGAppRefreshTaskRequest- Background fetch requests
App Lifecycle:
applicationWillTerminate- App termination notificationapplicationDidBecomeActive- App foreground notificationapplicationDidEnterBackground- App background notification
3. Implementation: ReactivationManager (iOS)
⚠️ Illustrative only – See Phase 1 and Phase 2 directives for canonical implementation.
ReactivationManager Responsibilities by Phase:
| Phase | Responsibilities |
|---|---|
| 1 | Cold start only (missed detection + verify/reschedule future) |
| 2 | Adds termination detection & recovery |
| 3 | Background task registration & boot recovery |
For implementation details, see:
3.1 Create New File
File: ios/Plugin/DailyNotificationReactivationManager.swift
Purpose: Centralized recovery logic for app launch scenarios
3.2 Class Structure
⚠️ Illustrative only – See Phase 1 for canonical implementation.
import Foundation
import UserNotifications
/**
* Manages recovery of notifications on app launch
* Handles cold start, warm start, and termination recovery scenarios
*
* @author Matthew Raymer
* @version 1.0.0
*/
class DailyNotificationReactivationManager {
private static let TAG = "DNP-REACTIVATION"
private let notificationCenter = UNUserNotificationCenter.current()
private let database: DailyNotificationDatabase
private let storage: DailyNotificationStorage
init(database: DailyNotificationDatabase, storage: DailyNotificationStorage) {
self.database = database
self.storage = storage
}
/**
* Perform recovery on app launch
* Detects scenario (cold/warm/termination) and handles accordingly
*/
func performRecovery() async {
do {
NSLog("\(Self.TAG): Starting app launch recovery")
// Step 1: Detect scenario
let scenario = try await detectScenario()
NSLog("\(Self.TAG): Detected scenario: \(scenario)")
// Step 2: Handle based on scenario
switch scenario {
case .termination:
try await handleTerminationRecovery()
case .coldStart:
try await handleColdStartRecovery()
case .warmStart:
try await handleWarmStartRecovery()
case .none:
NSLog("\(Self.TAG): No recovery needed")
}
NSLog("\(Self.TAG): App launch recovery completed")
} catch {
NSLog("\(Self.TAG): Error during app launch recovery: \(error)")
}
}
// ... implementation methods below ...
}
4. Recovery Scenario Detection
4.1 Scenario Detection Algorithm
Platform Reference: iOS §3.1.1 - Notifications survive app termination
Detection Logic:
enum RecoveryScenario {
case none // No recovery needed (first launch or warm resume)
case coldStart // App launched after termination, notifications may exist
case termination // App terminated, notifications missing vs DB
case warmStart // App resumed from background (optimization only)
}
func detectScenario() async throws -> RecoveryScenario {
// Step 1: Check if database has schedules
let schedules = try database.getEnabledSchedules()
if schedules.isEmpty {
return .none // First launch
}
// Step 2: Get pending notifications from UNUserNotificationCenter
let pendingNotifications = try await notificationCenter.pendingNotificationRequests()
// Step 3: Compare DB state with notification center state
let dbNotificationIds = Set(schedules.flatMap { $0.getScheduledNotificationIds() })
let pendingIds = Set(pendingNotifications.map { $0.identifier })
// Step 4: Determine scenario
if pendingIds.isEmpty && !dbNotificationIds.isEmpty {
// DB has schedules but no notifications scheduled
return .termination
} else if !pendingIds.isEmpty && !dbNotificationIds.isEmpty {
// Both have data - check if they match
if dbNotificationIds != pendingIds {
return .coldStart // Mismatch indicates recovery needed
} else {
return .warmStart // Match indicates warm resume
}
}
return .none
}
For complete implementation, see: Phase 1 directive
5. Missed Notification Detection
5.1 Detection Logic
Platform Reference: iOS §3.2.1 - App code does not run when notification fires
iOS Behavior: When a notification fires, the app code does NOT execute. The notification is displayed, but the app must detect missed notifications on the next app launch.
Detection Steps:
- Query database for notifications with
scheduled_time < currentTime - Filter for notifications with
delivery_status != 'delivered' - Mark as
'missed'in database - Record in history table
For complete implementation, see: Phase 1 directive
6. Background Task Registration
6.1 BGTaskScheduler Registration
Platform Reference: iOS §3.1.3 - Background tasks for prefetching
iOS Limitation: BGTaskScheduler cannot be used for critical scheduling. It's system-controlled and not guaranteed.
Use Case: BGTaskScheduler is used for:
- Prefetching content (not critical timing)
- Boot recovery (system may defer)
- Background maintenance (best effort)
Registration Location: AppDelegate.swift or SceneDelegate.swift
For complete implementation, see: Phase 3 directive
7. Testing Strategy
7.1 iOS Testing Tools
Simulator Testing:
xcrun simctl- Simulator control- Xcode Instruments - Performance profiling
- Console.app - System log viewing
Device Testing:
- Xcode Device Console - Real device logs
- Settings → Developer → Background App Refresh - Control background execution
7.2 Test Scenarios
Phase 1 Tests:
- Cold start recovery
- Missed notification detection
- Future notification verification
Phase 2 Tests:
- App termination detection
- Comprehensive recovery
- Multiple schedules recovery
Phase 3 Tests:
- Background task registration
- Boot recovery (simulated)
- Background task execution
For complete test procedures, see: iOS Test Scripts
8. Platform-Specific Notes
8.1 Notification Persistence
iOS Advantage: Notifications persist automatically across:
- App termination
- Device reboot (for calendar/time triggers)
App Responsibility: Must still:
- Detect missed notifications on app launch
- Reschedule future notifications if needed
- Track delivery status in database
8.2 Background Execution Limits
iOS Limitation: Background execution is severely limited:
- BGTaskScheduler is system-controlled
- Cannot rely on background execution for recovery
- Must handle recovery on app launch
Workaround: Use BGTaskScheduler for prefetching only, not for critical scheduling.
8.3 Timing Tolerance
iOS Limitation: Calendar-based notifications have ±180 second tolerance.
Impact: Notifications may fire up to 3 minutes early or late.
Mitigation: Account for tolerance in missed notification detection logic.
9. Next Steps
-
Start with Phase 1: Implement cold start recovery
- See Phase 1 directive
- Focus on missed notification detection
- Verify future notifications are scheduled
-
Proceed to Phase 2: Add termination detection
- See Phase 2 directive
- Implement comprehensive recovery
- Handle multiple schedules
-
Complete Phase 3: Background task registration
- See Phase 3 directive
- Register BGTaskScheduler handlers
- Implement boot recovery
10. References
- Platform Capability Reference - iOS OS-level facts
- Plugin Requirements - Requirements this directive implements
- Android Implementation Directive - Android equivalent for comparison
- iOS Recovery Scenario Mapping - Detailed scenario mapping
- iOS Core Data Migration Guide - Database migration guide
Document Version: 1.0.0
Last Updated: 2025-12-08
Next Review: After Phase 1 implementation