关于 axiom-core-location-ref
面向使用 Core Location 的 iOS 开发者的 API 参考,覆盖 iOS 17+ 的现代接口以及旧版模式。内容包括位置更新、地理围栏、授权和后台定位配置。
主要功能
- 01现代位置更新 API
- 02地理围栏与信标监控
- 03授权和精确度管理
- 04后台位置更新配置
- 05旧版 API 参考
适用场景
- 01实现地理围栏或区域监控
- 02配置后台位置跟踪
- 03排查定位授权与服务问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-core-location-ref安装后在 Claude Code 中输入/axiom-core-location-ref确认已加载。
详细介绍
Core Location 参考
现代 Core Location 的完整 API 参考(iOS 17+)。
适用场景
- 需要查询 CLLocationUpdate、CLMonitor、CLServiceSession 的 API 签名
- 实现地理围栏或区域监控
- 配置后台位置更新
- 了解授权模式
- 调试位置服务问题
相关技能
axiom-core-location— 反模式、决策树、压力场景axiom-core-location-diag— 基于症状的问题排查axiom-energy-ref— 将位置服务视为电池子系统(精度与功耗)
第 1 部分:现代 API 概述(iOS 17+)
四个关键类取代了旧版 CLLocationManager 模式:
| 类 | 用途 | iOS |
|---|---|---|
CLLocationUpdate | 用于位置更新的 AsyncSequence | 17+ |
CLMonitor | 基于条件的地理围栏/信标监控 | 17+ |
CLServiceSession | 声明式授权目标 | 18+ |
CLBackgroundActivitySession | 后台位置支持 | 17+ |
迁移路径:旧版 CLLocationManager 仍可使用,但新 API 提供:
- Swift 并发(async/await)
- 自动暂停/恢复
- 简化的授权
- 更高的电池效率
第 2 部分:CLLocationUpdate API
基本用法
import CoreLocation
Task {
do {
for try await update in CLLocationUpdate.liveUpdates() {
if let location = update.location {
// Process location
}
if update.isStationary {
break // Stop when user stops moving
}
}
} catch {
// Handle location errors
}
}
LiveConfiguration 选项
CLLocationUpdate.liveUpdates(.default)
CLLocationUpdate.liveUpdates(.automotiveNavigation)
CLLocationUpdate.liveUpdates(.otherNavigation)
CLLocationUpdate.liveUpdates(.fitness)
CLLocationUpdate.liveUpdates(.airborne)
根据使用场景选择。如果不确定,请使用 .default 或省略参数。
关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
location | CLLocation? | 当前位置(不可用时为 nil) |
isStationary | Bool | 设备停止移动时为 True |
authorizationDenied | Bool | 用户拒绝了位置访问权限 |
authorizationDeniedGlobally | Bool | 系统范围内的位置服务已停用 |
authorizationRequestInProgress | Bool | 正在等待用户决定是否授权 |
accuracyLimited | Bool | 精度降低(每 15-20 分钟更新一次) |
locationUnavailable | Bool | 无法确定位置 |
insufficientlyInUse | Bool | 无法请求授权(应用不在前台) |
自动暂停/恢复
设备静止时:
- 最后一次更新会以
isStationary = true和有效的location送达 - 更新暂停(节省电量)
- 设备移动后,更新会以
isStationary = false恢复
无需采取任何操作——此过程会自动发生。
AsyncSequence 操作
// Get first location with speed > 10 m/s
let fastUpdate = try await CLLocationUpdate.liveUpdates()
.first { $0.location?.speed ?? 0 > 10 }
// WARNING: Avoid filters that may never match (e.g., horizontalAccuracy < 1)
第 3 部分:CLMonitor API
用于监控地理条件和信标的 Swift actor。
基本地理围栏
let monitor = await CLMonitor("MyMonitor")
// Add circular region
let condition = CLMonitor.CircularGeographicCondition(
center: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.01),
radius: 100
)
await monitor.add(condition, identifier: "ApplePark")
// Await events
for try await event in monitor.events {
switch event.state {
case .satisfied: // User entered region
handleEntry(event.identifier)
case .unsatisfied: // User exited region
handleExit(event.identifier)
case .unknown:
break
@unknown default:
break
}
}
CircularGeographicCondition
CLMonitor.CircularGeographicCondition(
center: CLLocationCoordinate2D,
radius: CLLocationDistance // meters, minimum ~100m effective
)
BeaconIdentityCondition
三个粒度级别:
// All beacons with UUID (any site)
CLMonitor.BeaconIdentityCondition(uuid: myUUID)
// Specific site (UUID + major)
CLMonitor.BeaconIdentityCondition(uuid: myUUID, major: 100)
// Specific beacon (UUID + major + minor)
CLMonitor.BeaconIdentityCondition(uuid: myUUID, major: 100, minor: 5)
条件数量限制
每个应用最多 20 个条件。 请优先选择要监控的对象。如有需要,可根据用户位置动态切换区域。
添加时指定假定状态
// If you know initial state
await monitor.add(condition, identifier: "Work", assuming: .unsatisfied)
如果假设有误,Core Location 会进行更正。
访问记录
// Get single record
if let record = await monitor.record(for: "ApplePark") {
let condition = record.condition
let lastEvent = record.lastEvent
let state = lastEvent.state
let date = lastEvent.date
}
// Get all identifiers
let allIds = await monitor.identifiers
事件属性
| 属性 | 说明 |
|---|---|
identifier | 条件的字符串标识符 |
state | .satisfied、.unsatisfied、.unknown |
date | 状态变更时间 |
refinement | 对于通配符信标,表示检测到的实际 UUID/major/minor |
conditionLimitExceeded | 条件过多(最多 20 个) |
conditionUnsupported | 条件类型不可用 |
accuracyLimited | 精度降低,无法进行监控 |
关键要求
- 每个名称只能有一个监视器——同一时间只能有一个使用指定名称的实例
- 始终等待事件——只有在处理事件后,它才会成为
lastEvent - 启动时重新初始化——在
didFinishLaunchingWithOptions中重新创建监视器
第 4 部分:CLServiceSession API(iOS 18+)
声明式授权——告知 Core Location 你需要什么,而不是要它怎么做。
基本用法
// Hold session for duration of feature
let session = CLServiceSession(authorization: .whenInUse)
for try await update in CLLocationUpdate.liveUpdates() {
// Process updates
}
授权要求
CLServiceSession(authorization: .none) // No auth request
CLServiceSession(authorization: .whenInUse) // Request When In Use
CLServiceSession(authorization: .always) // Request Always (must start in foreground)
请求完整精度
// For features requiring precise location (e.g., navigation)
CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "NavigationPurpose" // Key in Info.plist
)
需要在 Info.plist 中配置 NSLocationTemporaryUsageDescriptionDictionary。
隐式会话
迭代 CLLocationUpdate.liveUpdates() 或 CLMonitor.events 会创建目标为 .whenInUse 的隐式会话。
若要禁用隐式会话:
<key>NSLocationRequireExplicitServiceSession</key>
<true/>
会话叠加
不要替换会话,而要叠加会话:
// Base session for app
let baseSession = CLServiceSession(authorization: .whenInUse)
// Additional session when navigation feature active
let navSession = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "Nav"
)
// Both sessions active simultaneously
诊断属性
for try await diagnostic in session.diagnostics {
if diagnostic.authorizationDenied {
// User denied—offer alternative
}
if diagnostic.authorizationDeniedGlobally {
// Location services off system-wide
}
if diagnostic.insufficientlyInUse {
// Can't request auth (not foreground)
}
if diagnostic.alwaysAuthorizationDenied {
// Always auth specifically denied
}
if !diagnostic.authorizationRequestInProgress {
// Decision made (granted or denied)
break
}
}
会话生命周期
会话会在以下情况下持续存在:
- App 进入后台
- App 暂停
- App 终止(由 Core Location 跟踪)
重新启动时,在 didFinishLaunchingWithOptions 中立即重新创建会话。
第 5 部分:授权状态机
授权级别
| 状态 | 说明 |
|---|---|
.notDetermined | 用户尚未作出决定 |
.restricted | 家长控制限制了访问 |
.denied | 用户明确拒绝 |
.authorizedWhenInUse | App 处于活动状态时允许访问 |
.authorizedAlways | 允许在后台访问 |
精度授权
| 值 | 说明 |
|---|---|
.fullAccuracy | 精确位置 |
.reducedAccuracy | 大致位置(约 5km),每 15-20 分钟更新一次 |
必需的 Info.plist 键
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show nearby places</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We track your location to send arrival reminders</string>
<key>NSLocationDefaultAccuracyReduced</key>
<true/>
旧版授权模式
@MainActor
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
enableLocationFeatures()
case .denied, .restricted:
disableLocationFeatures()
@unknown default:
break
}
}
}
第 6 部分:后台位置
要求
- 后台模式功能:Signing & Capabilities → Background Modes → Location updates
- Info.plist:添加包含
location值的UIBackgroundModes - CLBackgroundActivitySession 或 LiveActivity
CLBackgroundActivitySession
// Create and HOLD reference (deallocation invalidates session)
var backgroundSession: CLBackgroundActivitySession?
func startBackgroundTracking() {
// Must start from foreground
backgroundSession = CLBackgroundActivitySession()
Task {
for try await update in CLLocationUpdate.liveUpdates() {
processUpdate(update)
}
}
}
func stopBackgroundTracking() {
backgroundSession?.invalidate()
backgroundSession = nil
}
后台指示器
以下情况下会显示蓝色状态栏/胶囊:
- App 的授权为“使用 App 期间”
- App 在后台接收位置
- CLBackgroundActivitySession 处于活动状态
App 生命周期
- 前台 → 后台:会话继续运行
- 后台 → 暂停:会话保留,更新暂停
- 暂停 → 终止:Core Location 跟踪会话
- 终止 → 后台启动:立即重新创建会话
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Recreate background session if was tracking
if wasTrackingLocation {
backgroundSession = CLBackgroundActivitySession()
startLocationUpdates()
}
return true
}
第 7 部分:旧版 API(iOS 12-16)
CLLocationManager 代理模式
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = 10 // meters
}
func startUpdates() {
manager.startUpdatingLocation()
}
func stopUpdates() {
manager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
// Process location
}
}
精度常量
| 常量 | 精度 | 电池影响 |
|---|---|---|
kCLLocationAccuracyBestForNavigation | 约 5m | 最高 |
kCLLocationAccuracyBest | 约 10m | 很高 |
kCLLocationAccuracyNearestTenMeters | 约 10m | 高 |
kCLLocationAccuracyHundredMeters | 约 100m | 中 |
kCLLocationAccuracyKilometer | 约 1km | 低 |
kCLLocationAccuracyThreeKilometers | 约 3km | 很低 |
kCLLocationAccuracyReduced | 约 5km | 最低 |
旧版区域监控
// Deprecated in iOS 17, use CLMonitor instead
let region = CLCircularRegion(
center: coordinate,
radius: 100,
identifier: "MyRegion"
)
region.notifyOnEntry = true
region.notifyOnExit = true
manager.startMonitoring(for: region)
重大位置变化
用于粗略跟踪的低功耗替代方案:
manager.startMonitoringSignificantLocationChanges()
// Updates ~500m movements, works in background
到访监控
检测到达/离开:
manager.startMonitoringVisits()
func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
let arrival = visit.arrivalDate
let departure = visit.departureDate
let coordinate = visit.coordinate
}
第 8 部分:地理围栏最佳实践
区域大小
- 有效半径下限:约 100 米
- 较小区域:可能无法可靠触发
- 较大区域:更可靠,但精度较低
20 个区域限制策略
// Dynamic region management
func updateMonitoredRegions(userLocation: CLLocation) async {
let nearbyPOIs = fetchNearbyPOIs(around: userLocation, limit: 20)
// Remove old regions
for id in await monitor.identifiers {
if !nearbyPOIs.contains(where: { $0.id == id }) {
await monitor.remove(id)
}
}
// Add new regions
for poi in nearbyPOIs {
let condition = CLMonitor.CircularGeographicCondition(
center: poi.coordinate,
radius: 100
)
await monitor.add(condition, identifier: poi.id)
}
}
进入/退出时间
- 进入:通常在几秒到几分钟内
- 退出:离开后可能需要 3-5 分钟
- 精度取决于:蜂窝基站、WiFi、GPS 的可用性
持久性
- 条件会在多次启动 App 之间持续存在
- 启动时必须使用相同名称重新初始化监控器
- Core Location 会因事件唤醒 App
第 9 部分:测试与模拟
Xcode 位置模拟
- 在模拟器中运行
- Debug → Simulate Location → 选择位置
- 或使用自定义 GPX 文件
自定义 GPX 路线
<?xml version="1.0"?>
<gpx version="1.1">
<wpt lat="37.331686" lon="-122.030656">
<time>2024-01-01T00:00:00Z</time>
</wpt>
<wpt lat="37.332686" lon="-122.031656">
<time>2024-01-01T00:00:10Z</time>
</wpt>
</gpx>
测试授权状态
Settings → Privacy & Security → Location Services:
- 切换 App 的授权状态
- 切换系统范围的位置服务
- 测试降低的精度
控制台筛选
# Filter location logs
log stream --predicate 'subsystem == "com.apple.locationd"'
第 10 部分:Swift 并发集成
任务取消
let locationTask = Task {
for try await update in CLLocationUpdate.liveUpdates() {
if Task.isCancelled { break }
processUpdate(update)
}
}
// Later
locationTask.cancel()
MainActor 注意事项
@MainActor
class LocationViewModel: ObservableObject {
@Published var currentLocation: CLLocation?
func startTracking() {
Task {
for try await update in CLLocationUpdate.liveUpdates() {
// Already on MainActor, safe to update @Published
self.currentLocation = update.location
}
}
}
}
错误处理
Task {
do {
for try await update in CLLocationUpdate.liveUpdates() {
if update.authorizationDenied {
throw LocationError.authorizationDenied
}
processUpdate(update)
}
} catch {
handleError(error)
}
}
第 11 部分:地理编码
CLGeocoder — 正向地理编码(地址 → 坐标)
let geocoder = CLGeocoder()
func geocodeAddress(_ address: String) async throws -> CLLocation? {
let placemarks = try await geocoder.geocodeAddressString(address)
return placemarks.first?.location
}
// With locale for localized results
let placemarks = try await geocoder.geocodeAddressString(
"1 Apple Park Way",
in: nil, // CLRegion hint (optional)
preferredLocale: Locale(identifier: "en_US")
)
CLGeocoder — 反向地理编码(坐标 → 地址)
func reverseGeocode(_ location: CLLocation) async throws -> CLPlacemark? {
let placemarks = try await geocoder.reverseGeocodeLocation(location)
return placemarks.first
}
// Usage
if let placemark = try await reverseGeocode(location) {
let street = placemark.thoroughfare // "Apple Park Way"
let city = placemark.locality // "Cupertino"
let state = placemark.administrativeArea // "CA"
let zip = placemark.postalCode // "95014"
let country = placemark.country // "United States"
let isoCountry = placemark.isoCountryCode // "US"
}
CLPlacemark 关键属性
| 属性 | 示例 | 说明 |
|---|---|---|
name | "Apple Park" | 位置名称 |
thoroughfare | "Apple Park Way" | 街道名称 |
subThoroughfare | "1" | 门牌号 |
locality | "Cupertino" | 城市 |
subLocality | "Silicon Valley" | 街区 |
administrativeArea | "CA" | 州/省 |
postalCode | "95014" | 邮政编码 |
country | "United States" | 国家名称 |
isoCountryCode | "US" | ISO 国家代码 |
timeZone | America/Los_Angeles | 时区 |
location | CLLocation | 坐标 |
地理编码速率限制
- 一次只能发起一个请求 — 如果已有请求正在进行,CLGeocoder 会抛出异常
- Apple 会进行速率限制 — 限制请求频率,避免出现
kCLErrorGeocodeCanceled - 缓存结果 — 不要对相同的地址/坐标重复进行地理编码
- 谨慎批量处理 — 在连续的地理编码请求之间添加延迟
// Check if geocoder is busy
if geocoder.isGeocoding {
geocoder.cancelGeocode() // Cancel previous before starting new
}
故障排除速查
| 症状 | 检查项 |
|---|---|
| 没有位置更新 | 授权状态、Info.plist 键 |
| 后台功能不起作用 | 后台模式功能、CLBackgroundActivitySession |
| 始终授权未生效 | 使用 .always 的 CLServiceSession,并在前台启动 |
| 地理围栏未触发 | 区域数量(最多 20 个)、半径(最小约 100m) |
| 仅有降低精度的定位 | 检查 accuracyAuthorization,请求临时完整精度 |
| 位置图标一直显示 | 确保调用 stopUpdatingLocation(),或退出异步循环 |
资源
WWDC:2023-10180、2023-10147、2024-10212
文档:/corelocation、/corelocation/clmonitor、/corelocation/cllocationupdate、/corelocation/clservicesession
技能:axiom-core-location、axiom-core-location-diag、axiom-energy-ref