SwiftUI BackgroundTasks后台任务
SwiftUI BackgroundTasks后台任务

SwiftUI BackgroundTasks后台任务

BackgroundTasks 是苹果提供的后台执行框架,让 App 在退出后还能偶尔执行一些短任务,比如同步数据、清理缓存、预取内容,但系统决定什么时候运行。

基本概念

从iOS13+开始支持,提供的后台任务API BackgroundTasks,

可以提供两种类型的任务:

1、BGAppRefreshTask

偏轻量,用于拉取最新数据、同步一点点模型。

func scheduleAppRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "com.fjy.deposit.refresh")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60) // 1小时后最早可执行

    try? BGTaskScheduler.shared.submit(request)
}

2、BGProcessingTask

偏重量,适合需要更多时间的事情,例如:数据库清理、模型训练、文件处理。

func scheduleProcessing() {
    let request = BGProcessingTaskRequest(identifier: "com.fjy.deposit.process")
    request.requiresNetworkConnectivity = false
    request.requiresExternalPower = false

    try? BGTaskScheduler.shared.submit(request)
}

但必须申请权限,比如需要后台处理能力(Capabilities -> Background Modes -> Background Processing)。

注意:系统从不让应用随便在后台无限跑。它会根据“用户习惯 + 电量 + 网络 + 手机状态”等综合因素决定任务执行时间。

基本用法

1、引入框架

import BackgroundTasks

2、在Info.plist中注册任务ID

创建BGTaskSchedulerPermittedIdentifiers,内容是数组,每个元素是一个任务ID。

注意:在新的版本中,BGTaskSchedulerPermittedIdentifiers改为Permitted background task scheduler identifiers(允许的后台任务调度程序标识符)。

这里注册两个任务ID,分别用于BGAppRefreshTask和BGProcessingTask两种任务。

3、注册任务处理器

在App入口文件中,调用注册任务、任务处理方法和调度任务。

import SwiftUI
import BackgroundTasks

@main
struct TestMacApp: App {

    init() {
        registerTasks()
        scheduleAppRefresh()
        scheduleProcessing()
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

扩展中声明注册任务、任务处理方法和调度任务方法。

// MARK: - Background Task Registration
extension TestMacApp {
    func registerTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.fjy.deposit.refresh",
            using: nil
        ) { task in
            handleAppRefresh(task: task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.fjy.deposit.process",
            using: nil
        ) { task in
            handleProcessing(task: task as! BGProcessingTask)
        }
    }
}

// MARK: - Background Task Scheduling
extension TestMacApp {
    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.fjy.deposit.refresh")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
        try? BGTaskScheduler.shared.submit(request)
    }

    func scheduleProcessing() {
        let request = BGProcessingTaskRequest(identifier: "com.fjy.deposit.process")
        request.requiresNetworkConnectivity = false
        try? BGTaskScheduler.shared.submit(request)
    }
}

// MARK: - Handlers
extension TestMacApp {
    func handleAppRefresh(task: BGAppRefreshTask) {
        task.expirationHandler = { }
        // do work here...
        task.setTaskCompleted(success: true)
        scheduleAppRefresh()  // 调度下一次
    }

    func handleProcessing(task: BGProcessingTask) {
        task.expirationHandler = { }
        // do work here...
        task.setTaskCompleted(success: true)
        scheduleProcessing()
    }
}

当系统启动后台任务时,不会执行SwiftUI View代码,只会执行App初始化以及任务处理器,因此任务处理器必须在App入口文件的初始化中。

后台任务

1、后台调度器注册任务

下面这段代码是一个后台任务注册逻辑。

extension TestMacApp {
    func registerTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.fjy.deposit.refresh",
            using: nil
        ) { task in
            handleAppRefresh(task: task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.fjy.deposit.process",
            using: nil
        ) { task in
            handleProcessing(task: task as! BGProcessingTask)
        }
    }
}

任务标识符,需要在Info.plist中声明,using指定执行任务的DispatchQueue,nil表示主队列执行。

系统在后台启动任务时,会调用闭包,通过类型转换 as! BGAppRefreshTask 来确定具体任务类型。

2、任务处理方法

定义的任务处理方法用于处理实际的后台工作,比如拉取最新数据、更新数据库、上传日志。

extension TestMacApp {
    func handleAppRefresh(task: BGAppRefreshTask) {
        task.expirationHandler = { }
        // 处理后台任务
        task.setTaskCompleted(success: true)
        scheduleAppRefresh()  // 调度下一次
    }

    func handleProcessing(task: BGProcessingTask) {
        task.expirationHandler = { }
        // 处理后台任务
        task.setTaskCompleted(success: true)
        scheduleProcessing()
    }
}

1、expirationHander

task.expirationHandler = { }

expirationHandler是后台任务对象(BGTask)的属性,类型是一个闭包(() -) Void),系统在任务超时或后台任务到时会调用他。

它的作用时让App有机会清理资源、停止耗时操作、保存未完成的数据,避免被系统直接杀掉。

注意:必须调用 task.setTaskCompleted(success: true) 来告诉系统任务完成,否则系统会认为任务失败。

2、task.setTaskCompleted(success:)

func setTaskCompleted(success: Bool)

setTaskCompleted表示任务状态,传入true表示任务完成且成功,返回false表示任务失败或未完成。

必须调用该方法,如果不调用,系统会任务挂起太久,直接杀掉App进程。

如果返回false,系统会记录失败,不影响下一次任务调度。

setTaskCompleted主要用于记录任务完成情况,供系统统计和优化后台调度使用。

3、调度任务

当任务处理完成后,会调用调度任务。

这里定义了两个调度任务,分别是调度轻量后台任务(BGAppRefreshTask)和调度重量后台任务(BGProcessingTask)。

// MARK: - Background Task Scheduling
extension TestMacApp {
    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.fjy.deposit.refresh")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
        try? BGTaskScheduler.shared.submit(request)
    }

    func scheduleProcessing() {
        let request = BGProcessingTaskRequest(identifier: "com.fjy.deposit.process")
        request.requiresNetworkConnectivity = false
        try? BGTaskScheduler.shared.submit(request)
    }
}

BGAppRefreshTaskRequest是已经在Info.plist中声明的ID。

earliestBeginDate设置任务最早开始实际,这里是60秒。

如果不设置 earliestBeginDate,任务也不会立即执行。系统会根据后台调度策略,在合适时间触发。

requiresNetworkConnectivity 为设置任务网络,false表示不需要网络。

requiresExternalPower 表示是否必须插电才能执行。

BGTaskScheduler.shared.submit(request) 用于提交任务请求给系统,系统会根据电量、网络、后台活动等条件决定任务实际执行时间

注意事项

1、BackgroundTask不支持macOS

如果在Mac中配置BackgroundTask,Xcode会报错:

'BGTaskScheduler' is unavailable in macOS

因为Mac的后台执行机制由 launchd 管理,由系统运行,可以按照时间调度(定时任务)、监听事件。

总结

BackgroundTask可以实现后台任务的处理,但具体启用时间由系统决定。

iOS系统会根据以下因素决定什么时候执行

1、App 的使用频率(前台活跃度)

2、电量状态(低电量会延迟)

3、CPU / 内存占用情况

4、网络状态(如果 requiresNetworkConnectivity = true)

5、其他后台任务优先级

没有固定间隔,可能几分钟、几十分钟、甚至几小时才触发。

任务触发流程为

1、应用启动,registerTasks注册任务

2、系统决定触发任务,启动App

3、调用 BGTaskScheduler.shared.register 对应的闭包

4、传入一个任务对象(BGAppRefreshTask / BGProcessingTask)

5、调用任务处理方法(handleAppRefresh() / handleProcessing())

6、处理方法执行后台工作

7、调用 task.setTaskCompleted(success: true/false),告诉系统任务完成。

注意:系统不会重复执行任务,只有在任务处理方法中,调用调度任务方法,才会提交下一次任务,否则后台任务结束。

BackgroundTask有些类似iOS照片将图片同步到iCloud的操作,在后台中执行同步任务。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注