Background Execution Best Practices
If we want something to happen now or almost now, even when the app goes to the background, we need to call:
from main app:
UIApplication.beginBackgroundTask(expirationHandler:)from extensions:
ProcessInfo.performExpiringActivity(withReason:using:)
These objects give us more run time in the case our app is not in the foreground anymore. This api will ensure that our request goes through before the app gets suspended.
Discretionary Background URL Session
Defer the download to later, when is “later” is decided by the system (could be hours!).
This is for downloading content that is not high priority (or batch analytics) and to do so when is more proper.
Allows the system to defer the download until a better time
Provide information to system for smarter scheduling
// Set up background URL session
let config = URLSessionConfiguration.background(withIdentifier: "com.app.attachments")
let session = URLSession (configuration: config, delegate: ..., delegateQueue: ...)
// Set discretionary
config.discretionary = true We can even set a the earliest time when to start a download and an estimate workload size so the system can make some assumption on when it is the right time to do so.
Note that, if we put it too far in the future, and the user never uses the app again, our task might never be executed.
// Set time window
task.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60)
// Set workload size
task.countOfBytesClientExpectsToSend = 160
task.countOfBytesClientExpectsToReceive = 4096
task.resume() BackgroundTasks Framework
New in iOS 13
Example of tasks that fit perfectly with this framework:
Data Syncing
Database Cleanup
Backups Upload
Background Processing Tasks
Features:
Several minutes of runtime at system-friendly times
Option to turn off CPU Monitor for intensive work:
at night when the device is charging we can do battery intensive work without worrying
Background App Refresh Task
30 seconds of runtime to update the app every time
Called based on the user usage pattern: our app will get this refresh task before the user launches the app (again, based on the pattern) so that when the user launches the app all the new content has been downloaded already.
See
BGAppRefreshTask
How to use BackgroundTasks
We will interact mainly via the BGTaskScheduler, which is the interface to all of this intelligent scheduling.
To create a request, we will create an instance of either BGProcessingTaskRequest or BGAppRefreshTaskRequest, based on the task.
When the system is ready to give us time to do the task, the app will be launched (in the background) with a BGAppRefreshTask / BGAppProcessingTask.
Once we’re done, we will call setTaskCompleted and let the app be suspended.
Based on the tasks we’ve given to the BGTaskScheduler, our app will be given multiple tasks at the same time. However note that the time allotted for this is the same if it was one or multiple tasks.
All the tasks will be given to the main app, even when the background task was scheduled by an extension
HOW-TO
We need to have the background mode capability turned on with background fetch/processing ticked.
Set the
info.plistkeys to tell iOS we support app background task/refresh. Add a key “Permitted background task scheduler identifiers” and use an array of strings as the value. This array contains unique strings that our apps must declare, each string declares a different task that the app wants to perform.Go in the app delegate and deal with the task:
import BackgroundTasks
Register a background task.
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.colorfeed.refresh",
using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}Note how the last block of this function is what will be called when the system decides that it’s time to do the task.
Remember to call task.setTaskCompleted once it is done.
Lastly, schedule the task
func scheduleAppRefresh() {
let request =BGAppRefreshTaskRequest(identifier: "com.colorfeed.refresh")
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}Some cool properties of these requests:
earliestBeginDatetells the system to wait at least the time we pass to this method before calling the taskrequiresNetworkActivity(for processing only) tells the system to wake up our app for the task only if we have connectivity (or if we don’t need connectivity to do our task)requiresExternalPowertells the system we’re going to do intensive stuff and use lots of resources. Setting this totruealso disables CPU Monitor, allowing the app to do intensive work with no throttling.
Debugging
After scheduling the task, we can mock/force a system call to activate our scheduled task via the debugger with:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"TASK_IDENTIFIER"](replace TASK_IDENTIFIER with the task name.
