高效等待:SwiftUI 中的后台任务处理
Efficiency awaits: Background tasks in SwiftUI
2022年6月6日
一句话判断
SwiftUI 终于有了原生的后台任务 API,配合 Swift Concurrency 让后台调度代码的可读性提升了一个量级。
这场 Session 讲了什么
Apple 在 SwiftUI 中引入了一套全新的 BackgroundTask API,统一了 watchOS、iOS、tvOS、Mac Catalyst 和 Widgets 的后台任务处理方式。Session 以一个叫 Stormy 的示例应用贯穿全程——这是一个暴风雨天气拍照应用,会在正午检测天气,如果当天下雨就通知用户拍照上传。
核心工作流程分三步:
- 调度:在前台时通过
BGAppRefreshRequest预约后台唤醒时间 - 执行:系统在指定时间唤醒应用,发送后台任务
- 完成:应用处理完任务后系统重新挂起
这套 API 的最大卖点是全面拥抱 Swift Concurrency。过去处理后台任务需要嵌套多层 completion handler 和可变状态,现在可以用线性的 async/await 流程来写。
值得深挖的点
后台任务的生存周期管理是这次 Session 的重点。系统给后台任务的运行时间是有限的,当时间快用完时系统会发出信号。如果你的应用在这个信号到来前没有标记任务完成,应用可能被系统终止,还会被降低后续后台任务的调度优先级。
Background URLSession 的配合使用解决了”网络请求没完成但时间到了”的窘境。把网络请求设为 background 模式,应用可以立即完成当前后台任务进入挂起,等网络请求完成时系统会再次唤醒应用给一个新的 URLSession background task。
跨平台一致性是这套 API 的设计目标。同一个 backgroundTask modifier 在所有 Apple 平台上行为一致,你学一次就能到处用。这对于同时维护 iOS 和 watchOS 应用的团队来说是实打实的效率提升。
代码片段
调度后台刷新的核心代码:
// 调度明天正午的后台刷新
func scheduleAppRefresh() {
// 计算明天中午的时间
let noonTomorrow = Calendar.current.date(
byAdding: .day, value: 1, to: Date()
)!
let request = BGAppRefreshRequest()
request.earliestBeginDate = noonTomorrow
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("无法调度后台刷新: \(error)")
}
}
SwiftUI 中注册后台任务处理器:
@main
struct StormyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
// 注册后台任务处理器
.backgroundTask(.appRefresh("com.example.stormy")) {
// 继续调度明天的刷新
scheduleAppRefresh()
// 用 async/await 检查天气
let stormy = await isStormy()
if stormy {
// 如果是暴风雨天气,发送通知
await notifyForPhoto()
}
// 闭包返回时,系统自动标记任务完成
}
}
}
异步天气检查:
func isStormy() async -> Bool {
let url = URL(string: "https://weather.example.com/current")!
let (data, _) = try! await URLSession.shared.data(from: url)
let weather = try! JSONDecoder().decode(Weather.self, from: data)
return weather.isStormy
}
最佳实践
- 任务链式调度:每次后台任务开始时先调度下一次,确保刷新不会中断
- 优先使用 background URLSession:网络请求不要在后台任务中同步等待,用 background 模式让系统帮你管理网络生命周期
- 闭包结束即完成:backgroundTask modifier 的闭包返回时系统自动标记任务完成,不需要手动调用完成处理器
- 优雅处理取消:Swift Concurrency 的任务取消机制天然适配后台任务超时场景,用
Task.isCancelled检查当前状态 - 标识符要统一:调度请求和处理器中用同一个标识符字符串,系统才能正确匹配
还有什么值得关注
- 这套 API 对 Widget 的后台刷新同样适用,做小组件开发时值得留意
- Session 提到 iOS 应用在 Mac 上运行也支持这套 API,Mac Catalyst 适配成本进一步降低
- URLSession 的
data(from:)async 版本比传统的dataTask(with:completionHandler:)简洁太多,后台网络代码的维护成本会显著下降 - 对于需要精确时间调度的场景(比如每天固定时间检查),
earliestBeginDate只是一个”最早”时间,实际唤醒可能延后