MacKuba

Kuba Suder's blog on Mac & iOS development

Advances in App Background Execution

Categories: Foundation, WWDC 19 0 comments Watch the video

VoIP calls: when your app gets a pushRegistry(_: didReceiveIncomingPushWith: for: completion:) callback, it now has to start a call (reportNewIncomingCall) or the app will be killed

Background/silent pushes (content-available):

  • you must set apns-priority = 5
  • you should set apns-push-type = background

BackgroundTasks – a new background mode and framework for deferrable work

  • it lets you schedule some work to do in the background later at a more appropriate time, e.g. when the device is charging
  • several minutes of runtime
  • meant for e.g. maintenance tasks, ML training
  • you can request to turn off the CPU monitor that normally kills your app if you use too much CPU

New API for background refresh

  • works as before – 30 seconds of runtime, called throughout the day so that you keep your app up to date
  • the exact times and frequency are based on user’s usage patterns
  • old API (setMinimumBackgroundFetchInterval, performFetchWithCompletionHandler) is deprecated

Tasks/refresh are scheduled using requests sent to a BGTaskScheduler:

* BGAppRefreshTaskRequest – for app refresh (“a short task typically used to refresh content”)

* BGProcessingTaskRequest – for maintenance/learning tasks (“a time-consuming processing task”, “can take minutes to complete”)

Tasks can be scheduled from an extension, but are always run in the main app

Multiple tasks can be given to the app in one batch

Each type of task has a unique ID (preferably reverse-dns style), and an Info.plist key needs to list all task IDs

Scheduling tasks requires two steps:

1) Registering a task:

BGTaskScheduler.shared.register(forTaskWithIdentifier: “…”, using: queue/nil) { task in … }

In the task block, you can assign task.expirationHandler to be notified if the task needs to be killed before it finishes work

Call task.setTaskCompleted(success) when finished (or when expiration handler fires)

This needs to be done before the app finishes launching.

2) Actually scheduling a registered task:

let request = BGAppRefreshTaskRequest(identifier: “…”)
BGTaskScheduler.shared.submit(request)

request.earliestBeginDate – only start at or after this moment (don’t set more than a week into the future)

For processing tasks:

request.requiresNetworkConnectivity = true – default is false

request.requiresExternalPower = true – disables CPU monitor

You also need to schedule the next run of a task again once you’re in the task execution block, to keep the “refresh loop” going

If you call submit() during app initialization, run it on a background queue

Forcing a task to be scheduled from the debugger:

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@“…”]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@“…”]

Make sure that all files you need in a task are accessible when the device is locked

Tasks will not be run until the first device unlock after the reboot