关于 axiom-core-location
面向 Apple 平台开发者的 Core Location 决策技能,帮助选择授权方式、定位监测策略和精度,并检查耗电、后台定位及地理围栏问题。
主要功能
- 01选择 When In Use 或 Always 授权
- 02按需求选择定位监测方式
- 03匹配定位精度与使用场景
- 04处理拒绝授权和后台定位
- 05检查地理围栏与生命周期问题
适用场景
- 01设计按需升级的后台定位授权流程
- 02为地理围栏选择低耗电监测方式
- 03排查后台位置更新或围栏事件失效
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-core-location安装后在 Claude Code 中输入/axiom-core-location确认已加载。
详细介绍
Core Location 模式
用于 Core Location 实现决策的规范技能。可避免常见的授权错误、电池耗电和后台定位故障。
使用时机
- 选择授权策略(使用期间 vs 始终)
- 决定监测方式(持续监测 vs 重大位置变化 vs CLMonitor)
- 实现地理围栏或后台定位
- 调试“定位无法正常工作”的问题
- 检查定位代码中的反模式
相关技能
axiom-core-location-ref— API 参考、代码示例axiom-core-location-diag— 基于症状的故障排查axiom-energy— 定位作为电池子系统
第 1 部分:反模式(含时间成本)
反模式 1:过早请求始终授权
错误(拒绝率为 30-60%):
// First launch: "Can we have Always access?"
manager.requestAlwaysAuthorization()
正确(拒绝率为 5-10%):
// Start with When In Use
CLServiceSession(authorization: .whenInUse)
// Later, when user triggers background feature:
CLServiceSession(authorization: .always)
时间成本:修复代码需要 15 分钟,但 30-60% 的用户会永久拒绝授权 = 功能采用率受损。
原因:用户会拒绝过于激进的请求。先从最低限度开始,等用户了解其价值后再升级。
反模式 2:使用持续更新实现地理围栏
错误(电池耗电量增加 10 倍):
for try await update in CLLocationUpdate.liveUpdates() {
if isNearTarget(update.location) {
triggerGeofence()
}
}
正确(由系统管理,低功耗):
let monitor = await CLMonitor("Geofences")
let condition = CLMonitor.CircularGeographicCondition(
center: target, radius: 100
)
await monitor.add(condition, identifier: "Target")
for try await event in monitor.events {
if event.state == .satisfied { triggerGeofence() }
}
时间成本:重构需要 5 分钟,可节省 10 倍电量。
反模式 3:忽略静止状态检测
错误(浪费电量):
for try await update in CLLocationUpdate.liveUpdates() {
processLocation(update.location)
// Never stops, even when device stationary
}
正确(自动暂停/恢复):
for try await update in CLLocationUpdate.liveUpdates() {
if let location = update.location {
processLocation(location)
}
if update.isStationary, let location = update.location {
// Device stopped moving - updates pause automatically
// Will resume when device moves again
saveLastKnownLocation(location)
}
}
时间成本:添加检查需要 2 分钟,可显著节省电量。
反模式 4:未妥善处理授权拒绝
错误(用户体验受损):
for try await update in CLLocationUpdate.liveUpdates() {
guard let location = update.location else { continue }
// User denied - silent failure, no feedback
}
正确(平滑降级):
for try await update in CLLocationUpdate.liveUpdates() {
if update.authorizationDenied {
showManualLocationPicker()
break
}
if update.authorizationDeniedGlobally {
showSystemLocationDisabledMessage()
break
}
if let location = update.location {
processLocation(location)
}
}
时间成本:添加处理逻辑需要 10 分钟,可避免用户困惑。
反模式 5:精度与使用场景不匹配
错误(天气应用耗电):
// Weather app using navigation accuracy
CLLocationUpdate.liveUpdates(.automotiveNavigation)
正确(让精度匹配需求):
// Weather: city-level is fine
CLLocationUpdate.liveUpdates(.default) // or .fitness for runners
// Navigation: needs high accuracy
CLLocationUpdate.liveUpdates(.automotiveNavigation)
| 使用场景 | 配置 | 精度 | 电池消耗 |
|---|---|---|---|
| 导航 | .automotiveNavigation | ~5m | 最高 |
| 健身追踪 | .fitness | ~10m | 高 |
| 门店查找 | .default | ~10-100m | 中 |
| 天气 | .default | ~100m+ | 低 |
时间成本:更改需要 1 分钟,可显著节省电量。
反模式 6:未停止更新
错误(耗电,定位图标持续显示):
func viewDidLoad() {
Task {
for try await update in CLLocationUpdate.liveUpdates() {
updateMap(update.location)
}
}
}
// User navigates away, updates continue forever
正确(完成后取消):
private var locationTask: Task<Void, Error>?
func startTracking() {
locationTask = Task {
for try await update in CLLocationUpdate.liveUpdates() {
if Task.isCancelled { break }
updateMap(update.location)
}
}
}
func stopTracking() {
locationTask?.cancel()
locationTask = nil
}
时间成本:添加取消逻辑需要 5 分钟,可停止电池耗电。
反模式 7:忽略 CLServiceSession(iOS 18+)
错误(通过过程式方式反复处理授权):
func requestAuth() {
switch manager.authorizationStatus {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
if needsFullAccuracy {
manager.requestTemporaryFullAccuracyAuthorization(...)
}
// Complex state machine...
}
}
正确(声明式目标):
// Just declare what you need - Core Location handles the rest
let session = CLServiceSession(authorization: .whenInUse)
// For feature needing full accuracy
let navSession = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "Navigation"
)
// Monitor diagnostics if needed
for try await diag in session.diagnostics {
if diag.authorizationDenied { handleDenial() }
}
时间成本:迁移需要 30 分钟,可简化代码并减少错误。
第 2 部分:决策树
授权策略
Q1: Does your feature REQUIRE background location?
├─ NO → Use .whenInUse
│ └─ Q2: Does any feature need precise location?
│ ├─ ALWAYS → Add fullAccuracyPurposeKey to session
│ └─ SOMETIMES → Layer full-accuracy session when feature active
│
└─ YES → Start with .whenInUse, upgrade to .always when user triggers feature
└─ Q3: When does user first need background location?
├─ IMMEDIATELY (e.g., fitness tracker) → Request .always on first relevant action
└─ LATER (e.g., geofence reminders) → Add .always session when user creates first geofence
监测策略
Q1: What are you monitoring for?
├─ USER POSITION (continuous tracking)
│ └─ Use CLLocationUpdate.liveUpdates()
│ └─ Q2: What activity?
│ ├─ Driving navigation → .automotiveNavigation
│ ├─ Walking/cycling nav → .otherNavigation
│ ├─ Fitness tracking → .fitness
│ ├─ Airplane apps → .airborne
│ └─ General → .default or omit
│
├─ ENTRY/EXIT REGIONS (geofencing)
│ └─ Use CLMonitor with CircularGeographicCondition
│ └─ Note: Maximum 20 conditions per app
│
├─ BEACON PROXIMITY
│ └─ Use CLMonitor with BeaconIdentityCondition
│ └─ Choose granularity: UUID only, UUID+major, UUID+major+minor
│
└─ SIGNIFICANT CHANGES ONLY (lowest power)
└─ Use startMonitoringSignificantLocationChanges() (legacy)
└─ Updates ~500m movements, works in background
精度选择
Q1: What's the minimum accuracy that makes your feature work?
├─ TURN-BY-TURN NAV needs 5-10m → .automotiveNavigation / .otherNavigation
├─ FITNESS TRACKING needs 10-20m → .fitness
├─ STORE FINDER needs 100m → .default
├─ WEATHER/CITY needs 1km+ → .default (reduced accuracy acceptable)
└─ GEOFENCING uses system determination → CLMonitor handles it
Q2: Will user be moving fast?
├─ DRIVING (high speed) → .automotiveNavigation (extra processing for speed)
├─ CYCLING/WALKING → .otherNavigation
└─ STATIONARY/SLOW → .default
Always start with lowest acceptable accuracy. Higher accuracy = higher battery drain.
第 3 部分:压力场景
场景 1:“直接使用始终授权”
背景:PM 说:“用户想要位置提醒。首次启动时直接请求始终访问权限,这样功能就能正常工作。”
压力:快速交付,看起来更简单。
实际情况:
- 首次请求始终授权时,30-60% 的用户会拒绝
- 拒绝的用户只能在“设置”中重新启用(大多数人不会这么做)
- 用户还没理解功能价值,功能采用率就已受损
应对:
“首次请求始终授权时,拒绝率为 30-60%。我们应该先请求使用期间授权,然后在用户创建第一个位置提醒时再请求升级为始终授权。这样用户就能理解请求权限的原因,拒绝率可降至 5-10%。”
依据:Apple 在 WWDC 2024-10212 中的官方指南:“应主动创建 CLServiceSessions……当用户使用值得特别请求权限的功能时,持有一个要求完整精度的会话。”
场景 2:「后台运行时无法获取位置」
背景:QA 报告称「应用进入后台后就无法再获取位置」。
压力:需要在发布前快速修复。
错误的修复方式:
- 添加所有后台模式
- 在不了解其作用的情况下使用
allowsBackgroundLocationUpdates = true - 请求「始终」授权
正确的诊断步骤:
- 检查是否具备后台模式功能
- 检查是否持有 CLBackgroundActivitySession(未被释放)
- 检查会话是否从前台启动
- 检查授权级别(.whenInUse 可与 CLBackgroundActivitySession 配合使用)
回应:
「后台定位需要特定设置。我来检查:(1) 后台模式功能;(2) 跟踪期间是否持有 CLBackgroundActivitySession;(3) 会话是否从前台启动。缺少其中任何一项都会导致无法检测到的故障。」
检查清单:
// 1. Signing & Capabilities → Background Modes → Location updates
// 2. Hold session reference (property, not local variable)
var backgroundSession: CLBackgroundActivitySession?
func startBackgroundTracking() {
// 3. Must start from foreground
backgroundSession = CLBackgroundActivitySession()
startLocationUpdates()
}
场景 3:「地理围栏事件没有触发」
背景:地理围栏在测试时正常,但部分用户在生产环境中无法使用。
压力:「在我的设备上可以运行」式的推脱。
常见原因:
- 条件过多:每个应用最多 20 个
- 半径太小:要可靠触发,最小半径约为 100 米
- 区域重叠:可能造成混淆
- 未等待事件:事件只有在处理后才会成为 lastEvent
- 启动时未重新初始化:必须重新创建监视器
回应:
「地理围栏受多项系统限制。请检查:(1) 条件数量是否在 20 个以内?(2) 所有半径是否至少为 100 米?(3) 应用是否在启动时重新初始化 CLMonitor?(4) 应用是否始终在等待 monitor.events?」
诊断代码:
// Check condition count
let count = await monitor.identifiers.count
if count >= 20 {
print("At 20-condition limit!")
}
// Check all conditions
for id in await monitor.identifiers {
if let record = await monitor.record(for: id) {
let condition = record.condition
if let geo = condition as? CLMonitor.CircularGeographicCondition {
if geo.radius < 100 {
print("Radius too small: \(id)")
}
}
}
}
第 4 部分:检查清单
发布前位置检查清单
Info.plist:
-
NSLocationWhenInUseUsageDescription中包含清晰说明 - 如果使用「始终」授权,则添加
NSLocationAlwaysAndWhenInUseUsageDescription(清楚说明为何需要后台定位) - 如果可接受降低精度,则添加
NSLocationDefaultAccuracyReduced - 如果请求临时完整精度,则添加
NSLocationTemporaryUsageDescriptionDictionary - 如果需要后台跟踪,则在
UIBackgroundModes中加入location
授权:
- 从最低限度的授权(.whenInUse)开始
- 仅在用户触发后台功能时升级为 .always
- 优雅地处理授权被拒绝的情况(提供替代方案)
- 处理全局定位服务已关闭的情况
- 使用降低精度授权进行测试
更新:
- 根据使用场景采用合适的 LiveConfiguration
- 通过处理 isStationary 实现暂停和恢复
- 功能不活跃时取消位置任务
- 不要将连续更新用于地理围栏
测试:
- 测试授权被拒绝时的流程
- 测试降低精度模式
- 测试后台与前台之间的切换
- 测试应用终止后的重新启动恢复
后台定位检查清单
设置:
- 已添加后台模式功能(位置更新)
- 已创建并持有 CLBackgroundActivitySession(不能只是局部变量)
- 会话从前台启动
- 在 didFinishLaunchingWithOptions 中,于后台启动时重新启动更新
授权:
- 将 .whenInUse 与 CLBackgroundActivitySession 配合使用,或者
- 使用 .always(但仅限于后台指示器之外还需要其他功能时)
生命周期:
- 持久化「正在跟踪」状态,以便在重新启动后恢复
- 在后台启动时重新创建 CLBackgroundActivitySession
- 在启动时重新开始 CLLocationUpdate 迭代
- 在启动时使用相同名称重新初始化 CLMonitor
测试:
- 应用进入后台时显示蓝色后台定位指示器
- 应用进入后台后更新仍会继续
- 应用挂起后恢复时,更新会继续
- 应用终止后重新启动时,更新会继续
第 5 部分:iOS 版本注意事项
| 功能 | iOS 版本 | 说明 |
|---|---|---|
| CLLocationUpdate | iOS 17+ | AsyncSequence API |
| CLMonitor | iOS 17+ | 替代 CLCircularRegion |
| CLBackgroundActivitySession | iOS 17+ | 后台运行时显示蓝色指示器 |
| CLServiceSession | iOS 18+ | 声明式授权 |
| 隐式服务会话 | iOS 18+ | 通过遍历 liveUpdates 创建 |
| CLLocationManager | iOS 2+ | 旧版 API,但仍可使用 |
支持 iOS 14-16:使用 CLLocationManager 代理模式(参见 core-location-ref 第 7 部分)。
使用 iOS 17+:优先使用 CLLocationUpdate 和 CLMonitor。
使用 iOS 18+:添加 CLServiceSession,以实现声明式授权。
资源
WWDC:2023-10180、2023-10147、2024-10212
文档:/corelocation、/corelocation/clmonitor、/corelocation/cllocationupdate、/corelocation/clservicesession
技能:axiom-core-location-ref、axiom-core-location-diag、axiom-energy