We integrate BGTaskScheduler to efficiently manage background tasks in your iOS app. This guide covers registration, scheduling, and handling tasks with proper expirationHandler.
Why iOS restricts background tasks?
iOS aggressively optimizes power consumption: a backgrounded app gets 30 seconds to finish current work, then is suspended. BGTaskScheduler provides two task types that the system launches at opportune moments (e.g., charging or Wi-Fi). This is 10x more reliable than old methods like UIApplication backgroundTasks, as the system balances load and battery. About 90% of modern iOS apps use BGTaskScheduler for data updates.
What task types does BGTaskScheduler offer?
BGAppRefreshTask — short task (up to 30 seconds). System launches it under suitable conditions: device charging or Wi-Fi, user active. Suitable for updating interface data.
BGProcessingTask — long task (several minutes). Launched only when charging and Wi-Fi. Suitable for heavy operations: database migration, large content download, CoreML model retraining. BGProcessingTask is up to 50% less frequent than BGAppRefreshTask, conserving battery.
| Parameter | BGAppRefreshTask | BGProcessingTask |
|---|---|---|
| Maximum time | 30 seconds | Several minutes |
| Launch conditions | Charging or Wi-Fi (optional) | Charging + Wi-Fi |
| Typical scenarios | Interface data updates | DB migration, content download, ML |
| Launch frequency | Up to several times per hour | Less frequent |
How to implement registration and scheduling?
In Info.plist add task identifiers under BGTaskSchedulerPermittedIdentifiers:
<key>BGTaskSchedulerPermittedIdentifiers</key> <array> <string>com.yourapp.refresh</string> <string>com.yourapp.processing</string> </array> Registration in AppDelegate before applicationDidFinishLaunching:
import BackgroundTasks func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { BGTaskScheduler.shared.register( forTaskWithIdentifier: "com.yourapp.refresh", using: nil ) { task in self.handleRefresh(task: task as! BGAppRefreshTask) } BGTaskScheduler.shared.register( forTaskWithIdentifier: "com.yourapp.processing", using: nil ) { task in self.handleProcessing(task: task as! BGProcessingTask) } return true } Scheduling — when app enters background:
func scheduleAppRefresh() { let request = BGAppRefreshTaskRequest(identifier: "com.yourapp.refresh") request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) try? BGTaskScheduler.shared.submit(request) } func scheduleProcessing() { let request = BGProcessingTaskRequest(identifier: "com.yourapp.processing") request.requiresNetworkConnectivity = true request.requiresExternalPower = true request.earliestBeginDate = Date(timeIntervalSinceNow: 3600) try? BGTaskScheduler.shared.submit(request) } Schedule every time the app enters background via sceneDidEnterBackground or applicationDidEnterBackground. One submit call queues one task.
How to correctly handle the task and expirationHandler?
func handleRefresh(task: BGAppRefreshTask) { scheduleAppRefresh() let syncTask = Task { do { try await DataSyncService.shared.sync() task.setTaskCompleted(success: true) } catch { task.setTaskCompleted(success: false) } } task.expirationHandler = { syncTask.cancel() task.setTaskCompleted(success: false) } } expirationHandler is the most common overlooked mistake. If setTaskCompleted is not called before time expires, the system kills the process and marks the app as abusing background execution. After several such cases, iOS stops launching the app's tasks. In over 90% of cases, incorrect expirationHandler leads to task denial. Always set expirationHandler inside the handler, cancel all async operations, and call setTaskCompleted with false. Also reschedule the task for future.
When does the system launch tasks and how to debug?
Specific launch time cannot be guaranteed — iOS decides based on usage patterns, battery, network. Frequently used apps get more background time; new or rarely used apps get less.
Debugging in Xcode: tasks can be forced via Debug Menu while debugging on a real device: Xcode → Debug → Simulate Background Fetch Or via lldb:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"] Simulator does not support BackgroundTasks — a real device is required.
How to use URLSession for background downloads?
For background file uploads and downloads, BackgroundTasks is not needed — use URLSessionConfiguration.background(withIdentifier:). The system manages the transfer, and the app receives a callback on completion via AppDelegate.application(_:handleEventsForBackgroundURLSession:completionHandler:). This works even if the app was terminated.
Step-by-step implementation:
- Add background modes in Capabilities.
- Specify identifiers in Info.plist.
- Register handlers in AppDelegate.
- Implement scheduling on entering background.
- Write task logic with expirationHandler.
- Test on a real device via Debug.
- Upload to App Store Connect and pass review.
Process of implementing background tasks
- Configure provisioning profile and code signing for background mode support
- Register Permitted Identifiers in Info.plist
- Implement BGAppRefreshTask and BGProcessingTask handlers with correct expirationHandler
- Integrate URLSession background configuration for file transfers
- Schedule tasks according to app lifecycle
- Debug on real devices, set up APNs if needed
- Provide documentation and code review
What's included in our service
- Code review and optimization
- Detailed documentation and access to repositories
- Testing on real devices
- Post-launch support and updates
Timeline and cost
| Task | Timeline |
|---|---|
| Implement BGAppRefreshTask (data sync) | 1 day |
| Implement BGProcessingTask (heavy operations) | 1–2 days |
| URLSession background transfer | +0.5–1 day |
| Complete background infrastructure | 2–3 days |
Cost starts from $1,500 for basic implementation, prices vary by complexity. We provide a turnkey solution — just write to us for a free project assessment. With over 5 years of iOS development experience and 50+ successful integrations, we guarantee your app passes App Store Review and works correctly in background.







