Meet Core Location Monitor
Maps & Location 进阶 20m

认识 Core Location Monitor

Meet Core Location Monitor

2023年6月5日

在 Apple 官方观看视频

一句话判断

Core Location 团队终于把位置监听从 delegate 回调地狱中解救出来了——CLMonitor 是一个 Swift Actor,用三行代码就能实现地理围栏和 Beacon 监听,再也不用手动管理 CLLocationManager 的各种回调状态。

这场 Session 讲了什么

Core Location Frameworks 团队的 Nivash 介绍了全新的 CLMonitor API。这是一个基于 Swift Actor 的位置监听框架,核心用法极其简洁:创建 Monitor、添加条件、await 事件。

CLMonitor 是顶层 Swift Actor,每个实例是一个监听网关。因为它是 Actor,所以天然具备线程安全特性,不需要开发者手动处理任务同步问题。创建 Monitor 时传入一个字母数字字符串作为名称——如果同名 Monitor 已存在就打开它,否则创建新的。同一名称同一时刻只能有一个实例。

支持两种条件类型:CircularGeographicCondition(圆形地理区域,类似原来的 CLCircularRegion)和 BeaconIdentityCondition(Beacon 身份条件,类似原来的 CLBeaconIdentityConstraint)。每种条件添加后会被 Core Location 持续监控,状态变化通过事件记录通知。

Session 特别详细地讲解了 Beacon 的三级监控策略:只用 UUID 监控所有站点、UUID + Major 监控特定站点、UUID + Major + Minor 监控特定区域的特定位置。

值得深挖的点

Beacon 条件的通配符设计:BeaconIdentityCondition 支持 UUID、UUID+Major、UUID+Major+Minor 三种粒度。以 Apple 食堂为例——所有食堂的 Beacon 共享同一 UUID,每个站点用不同的 Major 值,站点内不同餐区用不同的 Minor 值。这种层级设计让一套 Beacon 基础设施同时服务于粗粒度和细粒度的位置感知。

记录系统(Records):每个被添加的条件都有一个对应的 Record 对象,包含条件本身、当前状态(unknown/satisfied/unsatisfied)、上次满足的时间戳和上次事件的时间戳。可以通过 identifier 查询单条记录,也可以获取所有记录。应用生命周期内随时可以检查这些记录。

初始状态覆盖:添加条件时可以通过 assuming: 参数指定初始状态。比如你的应用已经通过其他方式知道用户不在 Apple Park,就可以设置初始状态为 unsatisfied。不过不用担心——如果假设错了,Core Location 会在确定真实状态后纠正它。

代码片段

// 创建 Monitor 实例
let monitor = await CLMonitor("MyLocationMonitor")

// 添加圆形地理区域条件 - 监控用户是否到达公司
let workCenter = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)
let workCondition = CircularGeographicCondition(center: workCenter, radius: 100)
await monitor.add(workCondition, identifier: "Work", assuming: .unsatisfied)

// 添加 Beacon 条件 - 监控所有 Apple 食堂
let beaconUUID = UUID(uuidString: "12345678-1234-1234-1234-123456789ABC")!
let allCafeterias = BeaconIdentityCondition(uuid: beaconUUID)
await monitor.add(allCafeterias, identifier: "AllCafeterias")

// 监控特定站点
let parkSite = BeaconIdentityCondition(uuid: beaconUUID, major: 1)
await monitor.add(parkSite, identifier: "ApplePark")
// 监听事件变化 - 在后台持续监控
func startMonitoring() async {
    let monitor = await CLMonitor("MyLocationMonitor")
    let events = await monitor.events
    for await event in events {
        // event 包含 identifier 和 state
        switch event.identifier {
        case "Work":
            handleWorkStateChange(event.state)
        case "AllCafeterias":
            handleCafeteriaNearby(event.state)
        default:
            break
        }
    }
}

// 查询当前所有记录的状态
func checkAllRecords() async {
    let monitor = await CLMonitor("MyLocationMonitor")
    let records = await monitor.records
    for (identifier, record) in records {
        print("\(identifier): \(record.state)")
    }
}

最佳实践

  • 用有意义的 identifier:identifier 不仅是关联条件的键,也是后续查询记录和移除条件的依据。“Work”、“Home” 这样的命名比 “condition1” 好得多。
  • 合理使用 assuming 参数:如果你的应用已经有位置上下文信息,用它来设置初始状态可以减少 Core Location 的判断时间。但不要滥用——错误的初始假设不会导致问题,但会增加状态切换的延迟。
  • 移除不再需要的条件:用 remove(identifier:) 清理不再需要的监听条件,减少不必要的系统资源消耗。
  • 注意 Monitor 的单例限制:同一名称同一时刻只能有一个打开的 Monitor 实例。不要在多个地方创建同名 Monitor。
  • 权限仍然是前提:CLMonitor 不改变位置权限的请求方式,仍然需要在使用前获取适当的位置权限。

还有什么值得关注

  • CLMonitor 的 events 是一个 AsyncSequence,可以 for-await 循环监听——这是 Apple 在系统框架中全面推进 Swift Concurrency 的又一个例证。
  • Beacon 的三级监控策略(UUID only / UUID+Major / UUID+Major+Minor)是一个很好的架构范式,值得在设计多层级位置感知系统时参考。
  • Record 中包含 lastSatisfiedAt 和 lastEventAt 时间戳,这对分析用户的行为模式(比如用户通常什么时候到公司)很有用。
  • Session 强调 CLMonitor 是 Actor 而非 Class——这意味着所有属性的访问都需要 await,但同时也意味着你不需要担心并发安全问题。
  • 旧的 CLCircularRegion 和 CLBeaconRegion API 仍然可用,但新项目建议直接使用 CLMonitor。
WWDC 2023