探索简化的定位更新 API
Discover streamlined location updates
2023年6月5日
一句话判断
CoreLocation 终于有了基于 Swift Concurrency 的定位 API——CLLocationUpdate.liveUpdates() 一行代码就能开始接收定位,配合 AsyncSequence 的全部能力,这是多年来定位 API 最大的使用体验升级。
这场 Session 讲了什么
Apple 为 CoreLocation 引入了全新的 Swift 原生定位更新 API,核心是 CLLocationUpdate 类及其 liveUpdates() 静态方法。这个方法返回一个 AsyncSequence,可以直接用 for try await 迭代获取定位更新。
主要内容分为五个部分:
- 新 API 结构:
CLLocationUpdate包含location(CLLocation 类型,可为 nil)和isStationary(Bool)两个属性。liveUpdates()接受可选的LiveConfiguration枚举参数。 - 前台定位:从 import CoreLocation 到 for-try-await 循环,整个过程极其简洁。停止更新只需 break 循环。
- 后台定位:两种方式——Live Activity(推荐)和新增的
CLBackgroundActivitySession。后者显示蓝色定位指示器,让 App 在后台保持”使用中”状态。 - 自动暂停与恢复:设备静止一段时间后,
isStationary变为 true,App 可以据此暂停处理。设备再次移动时自动恢复。 - App 生命周期:新 API 在各种 App 状态下的行为说明。
LiveConfiguration 枚举提供了预设配置:default、automotiveNavigation、otherNavigation、fitness、airborne,对应不同的使用场景。这些配置和旧的 CLActivityType 一一对应。
值得深挖的点
AsyncSequence 的全部能力可以直接用在定位流上。你可以用 .first(where:) 过滤特定条件的定位,用 .filter 排除低精度更新,用 .map 转换数据。Session 中展示了一个例子:用 .first 找到速度超过 200 米/秒的定位——但讲者也特意警告了这个速度值不太合理,提醒开发者注意 filter 条件的设置。
CLBackgroundActivitySession 的持有生命周期是个容易踩坑的地方。Session 反复强调必须把 session 对象赋值给属性(self.backgroundActivity),而不是局部变量。局部变量出了作用域就会被释放,session 随之失效,后台定位就断了。这个设计是有意为之——通过对象生命周期来控制后台定位权限。
isStationary 的设计哲学值得注意。Apple 没有简单地停止发送更新,而是通过标志位让开发者自己决定如何处理。这给了开发者更多控制权——你可以选择 break 退出循环,也可以选择继续监听但降低处理频率。
代码片段
最简化的前台定位:
import CoreLocation
// 一行开始定位更新
for try await update in CLLocationUpdate.liveUpdates() {
guard let location = update.location else { continue }
// 使用 location 进行处理
print("当前位置:\(location.coordinate)")
// 设备静止时停止更新
if update.isStationary {
break // 简单地退出循环即可停止
}
}
使用 AsyncSequence 的过滤能力:
// 使用 LiveConfiguration 指定场景
let updates = CLLocationUpdate.liveUpdates(.automotiveNavigation)
// 找到第一个速度大于特定值的定位
if let moving = try await updates.first(where: { update in
guard let loc = update.location else { return false }
return loc.speed > 5.0 // 注意:单位是米/秒
}) {
print("检测到移动:\(moving.location?.speed ?? 0) m/s")
}
后台定位的完整方案:
class LocationManager {
// 必须作为属性持有,否则会被释放导致后台定位中断
var backgroundActivity: CLBackgroundActivitySession?
func startBackgroundUpdates() {
// 创建后台活动 session,显示蓝色定位指示器
backgroundActivity = CLBackgroundActivitySession()
Task {
for try await update in CLLocationUpdate.liveUpdates() {
guard let location = update.location else { continue }
// 处理后台定位更新
}
}
}
func stopBackgroundUpdates() {
// 显式失效或让对象释放
backgroundActivity?.invalidate()
backgroundActivity = nil
}
}
最佳实践
LiveConfiguration的选择要匹配实际使用场景。健身类 App 用fitness,导航类用automotiveNavigation。错误的配置会导致定位精度和功耗表现不理想。- 在
for await循环中务必处理location为 nil 的情况。系统在定位不可用时会发送 nil 更新,忽略它可能导致 crash。 - 使用 AsyncSequence 的 filter/first 等操作时,确保条件合理。一个永远无法满足的 filter 意味着循环永远不会结束,定位更新永远不会停止。
- 后台定位需要
UIBackgroundModes中包含location,这点没变。CLBackgroundActivitySession不是绕过系统限制的手段,而是让已有的后台定位能力更容易使用。 - 优先使用 Live Activity 来支持后台定位,用户体验更好。
CLBackgroundActivitySession是给没有 Live Activity 的 App 的备选方案。 isStationary为 true 时建议 break 退出循环,但如果你需要持续监听(比如物流追踪),可以继续迭代但降低处理频率。
还有什么值得关注
- 新 API 完全替代了旧的
CLLocationManagerDelegate模式,但旧 API 不会废弃,现有代码无需立即迁移。 CLBackgroundActivitySession同时支持定位更新和CLMonitor事件监控,一个 session 覆盖两种能力。- Session 没有深入讨论定位精度控制,如果你需要精细控制更新频率和精度,可能仍需结合旧的
CLLocationManager的desiredAccuracy设置。 - 自动暂停/恢复机制依赖设备的运动协处理器,模拟器上可能无法完整测试这一行为。
- 与 WidgetKit 和 Live Activity 的配合是今年的大方向,定位类 App 值得重新审视整体架构。