mcpField
A
axiom-core-location

axiom-core-location

github.com/charleswiltgen开发1.2k173 次安装今天更新
代码仓库

关于 axiom-core-location

面向 Apple 平台开发者的 Core Location 决策技能,帮助选择授权方式、定位监测策略和精度,并检查耗电、后台定位及地理围栏问题。

主要功能

  1. 01选择 When In Use 或 Always 授权
  2. 02按需求选择定位监测方式
  3. 03匹配定位精度与使用场景
  4. 04处理拒绝授权和后台定位
  5. 05检查地理围栏与生命周期问题

适用场景

  1. 01设计按需升级的后台定位授权流程
  2. 02为地理围栏选择低耗电监测方式
  3. 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
  • 请求「始终」授权

正确的诊断步骤:

  1. 检查是否具备后台模式功能
  2. 检查是否持有 CLBackgroundActivitySession(未被释放)
  3. 检查会话是否从前台启动
  4. 检查授权级别(.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:「地理围栏事件没有触发」

背景:地理围栏在测试时正常,但部分用户在生产环境中无法使用。

压力:「在我的设备上可以运行」式的推脱。

常见原因:

  1. 条件过多:每个应用最多 20 个
  2. 半径太小:要可靠触发,最小半径约为 100 米
  3. 区域重叠:可能造成混淆
  4. 未等待事件:事件只有在处理后才会成为 lastEvent
  5. 启动时未重新初始化:必须重新创建监视器

回应:

「地理围栏受多项系统限制。请检查:(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 版本说明
CLLocationUpdateiOS 17+AsyncSequence API
CLMonitoriOS 17+替代 CLCircularRegion
CLBackgroundActivitySessioniOS 17+后台运行时显示蓝色指示器
CLServiceSessioniOS 18+声明式授权
隐式服务会话iOS 18+通过遍历 liveUpdates 创建
CLLocationManageriOS 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

评论

支持 Markdown