I. The Dream: Silent Guardian
Every developer using location or time-sensitive data dreams of it: the app that works smartly in the background. For my earthquake alert app, iQuake, the goal seemed straightforward: use SwiftUI's modern tools to periodically check USGS (USA/Global) and TMD (Thailand) feeds in the background. When a significant quake happened near the user, pop up a helpful local notification. Simple, right? I reached for the shiny new .backgroundTask()
modifier and BGAppRefreshTaskRequest
, expecting a relatively smooth integration. Oh, how optimistic I was.
II. Laying the Foundation: Data Wrangling
- Fetch from Two Worlds: Grab data from USGS (GeoJSON) and TMD (RSS/XML). Swift concurrency (
async let
) made fetching both at once a breeze. - Parse the Puzzle: JSON vs. XML. USGS was easy. TMD meant rolling with
XMLParserDelegate
. - Stop the Spam: Deduplication via a
Set<String>
of earthquake IDs inUserDefaults
. Crucial. - Is it Relevant? Magnitude filter, distance (CoreLocation), and quiet hour checks – all via
UserDefaults
-backed settings.
III. The First Wall: The Sound of Silence
The background task handler simply refused to run. Not a peep in logs. Simulate Background Fetch? Nothing. Device idle and plugged in? Still nothing. Cue the epic debugging montage: Info.plist entries scrutinized. App reinstallations. Simulator resets. Desperation setting in.
IV. Plot Twist: Crashes, dasd, and the AppDelegate Detour
Then it crashed – hard. SIGABRT via NSAssertionHandler
deep inside BGTaskScheduler
. Even with .backgroundTask
commented out! Enter the AppDelegate fix:
- Added
@UIApplicationDelegateAdaptor
. - Registered BG task in
didFinishLaunchingWithOptions
. - Moved task logic into AppDelegate with
Task { ... }
anddefer { task.setTaskCompleted(success:) }
.
Crashes? Gone. Success? Not quite. dasd
logs showed the task was submitted... but not allowed to run. "DecisionToRun:0." So close, yet so far.
V. Light at the End of the Tunnel (and an iPad Blind Spot)
Breakthrough! Using LLDB
: e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@...]
forced execution of the handler. It worked! Then, discovered iPad notifications weren't appearing due to missing authorization code. Added:
UNUserNotificationCenter.current().requestAuthorization(...)
in launch. Notifications: unlocked.
And finally, after weeks of debugging... a plugged-in, idle iPhone triggered a real notification from the background task. Victory.
VI. Lessons Learned (The Hard Way)
- BGTasks are a Suggestion, Not a Command: You submit. iOS decides.
- Use the Console: Filter for
dasd
andBackgroundTasks
. - Simulators Are Liars: Only trust real devices for testing.
- Plist Perfection: One typo = weeks of pain.
- Permissions Are Not Optional: Notification setup must be explicit.
- Deduplication is Your Friend: Users hate repeat alerts.
- AppDelegate Still Rules: Sometimes old school wins.
This was a wild debugging ride — one that tested patience, persistence, and plenty of coffee. Hope this saves someone else from the same fate.