How to effectively distribute and defer work from the foreground to the background.
Previously, we had a good overview of how the Background Execution is handled after iOS 13. Now is time to get our hands on it. In this article I will share with you my own implementation and my findings.
It mainly schedules 2 tasks (one of each type) when the app goes into the background and when the task is performed, it records in a local storage the time of execution and the task nature.
My goal was to show when the system was opening my app to perform what task. Let me walk you through it step by step:
There are some configuration to be set in order to add tasks to the Background Scheduler. Apple has a great documentation for that with helpful resources (which you can check here). Overall, you must:
- In your Xcode, under the Project Settings “Signing & Capabilities” Tab, add a Background Mode Capability;
- Tick the Background Fetch and/or Background Processing box;
- Add this key to your
Info.plist: BGTaskSchedulerPermittedIdentifiers. It is a list of Strings. You should append all your Background Task Identifiers on it;
Registering Background Tasks
Once these steps are done, you can then register the identifiers in the
func register(forTaskWithIdentifier: String, using: DispatchQueue?, launchHandler: (BGTask) -> Void) -> Bool. Preferably, you should do it when your app is about to finish launching.
Now you can schedule the background task whenever you find suitable.
When the system is ready to perform your task in the background, it will wake up your app and put it in the background state. If it was previously terminated, it will launch it first and then immediately put in the background. Otherwise, it will transition from the suspended state to the background.
Scheduling Background Tasks
In order to schedule background tasks, we need to submit requests to the
BGTaskScheduler. These requests will hold the conditions specific to the task we want to run, such as the earliest time to begin the task.
To schedule an App Refresh Task, you will need to submit a
BGAppRefreshTaskRequest. Optionally, it allows you to determine a start time to run the task.
For Processing Tasks, submit a
BGProcessingTaskRequest instead. You will be able to determine if the task requires external power and network connectivity.
Testing Background Tasks
We don’t control how or when the Background Scheduler launches our tasks. In fact, it is arbitrary. This is because the Scheduler will prioritise saving energy.
It is possible, though, to simulate the launch and expiration of a background task by running in the debug console the following instructions:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@“…”]e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@“…”]
To do so, your app should be running in foreground and you need to have scheduled a task previously.
I’ve been checking this app every morning and by the evening. Accordingly to the Battery Life Software Team in Apple, the system will “learn” how you use the app and try to perform the App Refresh Tasks slightly before you actually open the app, so you can have the feeling of an always up-to-date app.
In fact, that’s exactly what I’ve noticed. The system woke up my app roughly before the average hour I was used to open it (which turns to be in between my breakfast and lunch time).
On the other hand, with the Processing Task I’ve noticed it was running only at night (even though I kept my app connected to the power the whole day long).
Although you may set that you don’t need external power, the system will likely prefer to execute the launch handlers when the device is recharging.
We have reviewed some features and limitations of Background Tasks and behaviours of Background Execution. Below I’ve listed my main findings as well as my recommendations to you:
- Background execution is way more restricted scope than Foreground execution.
- Your tasks will rely on the user’s circumstances to be launched.
- Background Tasks are arbitrary. The system is optimised to reduce energy usage and increase device’s battery life. So we should not rely on the background to perform important work.
- Do not schedule important business logic tasks in the background. You are not guaranteed when they will be run.
- Do not schedule background tasks to be executed in a point far in the future.
- If you re-subscribe a task with the same identifier, it will override the previous schedule.
- Prevent your app to be terminated by the system while it is working on your background tasks. Set the task as completed if you finish earlier. This will keep your app in memory (suspended state) preventing it to be relaunched.
- You have roughly 30 seconds to perform all your background tasks, not each one. Do not surpass this limit and balance it gracefully. Otherwise the system can stop launching your tasks.
- Processing tasks run only when the device is idle. The system terminates any background processing tasks running when the user starts using the device. Background refresh tasks are not affected.
- Your development should be driven by the user experience. Think about your user first. Recognise your app isn’t the only one running.
Apple has a wide range of background execution APIs for all sorts of use cases you should definitely check out. You can find more information in Choosing Background Strategies for Your App.
Thanks for reading! I’m looking forward to know what problems we can solve with this! If you enjoyed this article and want to say the word, please contact me in twitter. Happy coding! 👨🏻💻