关于后台任务的选择

众所周知,iOS 没有真正的后台,而是所谓的“假后台”。那么如果确实有需要在后台完成的任务需要怎么做呢?有如下几种方法可供选择。

1. 在后台继续前台未完成的工作

在某些情况下,用户在前台有未完成任务的时候就将应用切到了后台或者是锁屏,这个时候,任务不会继续执行,除非我们能够让其在后台能够继续执行。
比如一个图片下载任务,如果切到了后台,为了让其能够继续下载,需要在开启下载任务的同时开始一个后台任务,这样在应用切到后台的时候,可以在有限时间内继续下载。

此处使用 UIApplicationbeginBackgroundTask 方法来开启后台任务。这个方法提供了一个后台任务截止时间的回调并返回一个后台任务标识符,可通过标识符来结束后台任务。如果没有在指定时间内手动结束后台任务,则 expirationHandler 会被调用,这是最后一次能够处理任务的时机,可以发送任务状态的通知等。

1
2
3
4
5
6
7
8
9
10
11
12
func download(_ url: URL) {
let downloadOperation = DownloadOperation(with: url)
let identifier: UIBackgroundTaskIdentifier!
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
downloadOperation.cancel()
print("Download task not finished.")
})
downloadOperation.completionHandler = {
UIApplication.shared.endBackgroundTask(identifier)
}
downloadOperation.download()
}

2. BackgroundTasks

在 iOS 13之前,我们使用如下方式来执行后台任务:

1
2
UIApplication.shared.setMinimumBackgroundFetchInterval(60)
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {}

但是在 iOS 13之后,上述方法已经被弃用,被一个新的后台任务框架 BackgroundTasks 所取代。
BackgroundTasks 有两种后台任务可供选择,一种是 BGProcessingTask ,一般用于处理较为繁重的任务,如数据维护,机器学习模型训练,数据同步等。另外一种是 BGAppRefreshTask ,用于在后台更新应用内容,这样用户每次打开应用时所查看到的内容都是最新的。BGAppRefreshTask 有最多 30 秒的时间来执行更新任务,任务完成之后要调用 setTaskCompleted(success:),否则系统会终止应用程序。
详情请查看 [[BackgroundTasks 实践#^a71227|BackgroundTasks]]

3. 利用推送执行后台任务

3.1 后台推送

在 iOS7 的时候,苹果允许应用收到通知后在后台状态下运行一段代码,这样应用就可以在不打扰用户的情况下静默更新内容。如果应用会不定期地从服务器获取新内容,在新内容可用时就可使用后台推送来通知我们的应用。
推送一个后台通知需要满足如下设置:

  • content-available 设置为 1;
  • 不设置 alertsoundbadge;
  • apns-push-type 设置为 background
  • apns-priority 设置为5。
    一个典型的后台 APNs 如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "aps": {
    "content-available": 1,
    "apns-push-type": "background",
    "apns-priority": 5
    },
    "additional": "background push notification"
    }
    后台通知优先级较低,因此系统并不能保证其一定能被送达。另外要注意时间和频率的限制。一旦应用接收到后台推送,会调用系统代理application(_:didReceiveRemoteNotification:fetchCompletionHandler:)。只有最多 30s 的时间去完成相应的工作,只要完成了后台任务,就要尽可能早地调用 fetchCompletionHandler 以节省电池。苹果建议每小时最多不要发送超过3条后台推送,否则系统会增加速率限制。

在同一时间,只会存在最新的一条后台推送,如果用户手动杀掉应用程序后台,系统会丢弃已收到的后台通知。如果用户启动了应用程序,则系统会立即发送所收到的通知。

App的状态限制
后台推送的前提是应用需处于 background 或 suspended 状态,如果应用是被用户手动杀掉的话则无法被唤醒并处理后台推送。

3.2 推送服务扩展

通过通知服务扩展,我们也可以在收到通知后进行一些处理后再将通知传达给用户。
新建 UNNotificationServiceExtension,当收到通知时,通知服务扩展会被唤醒同时获得后台任务的执行时间。此时 didReceive(_:withContentHandler:) 会被调用,当完成所需要处理的任务时,调用 contentHandler 来结束整个流程。

通知服务扩展需要将推送通知的 mutable-content 设置为1。

参考资料
Advances in App Background Execution