mcpField
A
axiom-core-location

axiom-core-location

github.com/charleswiltgen開発1.2k173 回インストール今日更新
リポジトリ

axiom-core-location について

Apple プラットフォームの開発者向け Core Location の設計スキルです。権限、監視方法、精度の選択を案内し、バッテリー消費やバックグラウンド位置情報、ジオフェンスの問題を調査します。

主な機能

  1. 01When 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 の実装に関する判断のための規律スキルです。よくある認可の誤り、バッテリー消費、バックグラウンドでの位置情報取得の失敗を防ぎます。

使用する場面

  • 認可方式を選ぶとき(使用中のみと常に許可)
  • 監視方式を決めるとき(継続的な更新、重要な位置変化、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 分の 1 に抑えられます。


アンチパターン 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:拒否された場合の適切な処理がない

誤り(UX が損なわれる):

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. 認証レベルを確認する(CLBackgroundActivitySession では .whenInUse が機能します)

対応:

「バックグラウンドで位置情報を取得するには、特定の設定が必要です。確認します。(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 m が必要です
  3. 領域が重なっている:混乱を招く可能性があります
  4. イベントを待機していない:イベントは処理されてからでないと lastEvent になりません
  5. 起動時に再初期化していない:モニターを再作成する必要があります

対応:

「ジオフェンシングには、システム上の制約がいくつかあります。確認してください。(1)条件数は上限の 20 件以内ですか?(2)すべての半径が 100 m 以上ですか?(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 に明確な説明を記載する
  • Always を使用する場合は NSLocationAlwaysAndWhenInUseUsageDescription を設定する(バックグラウンドで必要な理由を明確にする)
  • 精度を下げても問題ない場合は NSLocationDefaultAccuracyReduced を設定する
  • 一時的に完全な精度をリクエストする場合は NSLocationTemporaryUsageDescriptionDictionary を設定する
  • バックグラウンドで追跡する場合は UIBackgroundModes に location を含める

認証:

  • 最小限の認証(.whenInUse)から開始する
  • ユーザーがバックグラウンド機能を開始した場合にのみ .always に引き上げる
  • 認証が拒否された場合は適切に処理する(代替手段を提示する)
  • 位置情報サービス全体が無効になっている場合に対応する
  • 精度を下げた状態での認証をテストする

更新:

  • ユースケースに適した LiveConfiguration を使用する
  • 一時停止/再開のために isStationary を処理する
  • 機能が非アクティブな場合は位置情報タスクをキャンセルする
  • ジオフェンシングに継続的な更新を使用しない

テスト:

  • 認証拒否時のフローをテストした
  • 精度を下げたモードをテストした
  • バックグラウンドからフォアグラウンドへの遷移をテストした
  • アプリ終了後の再起動時に復旧できることをテストした
バックグラウンド位置情報チェックリスト

設定:

  • バックグラウンドモード機能(位置情報の更新)を追加した
  • CLBackgroundActivitySession を作成し、保持している(ローカル変数にしていない)
  • セッションをフォアグラウンドから開始した
  • didFinishLaunchingWithOptions で、バックグラウンド起動時に更新を再開する

認証:

  • CLBackgroundActivitySession とともに .whenInUse を使用している、または
  • .always を使用している(ただし、バックグラウンドインジケーター以上の機能が必要な場合に限る)

ライフサイクル:

  • 再起動時の復旧に備えて「追跡中だった」状態を永続化している
  • バックグラウンド起動時に CLBackgroundActivitySession を再作成している
  • 起動時に CLLocationUpdate の反復処理を再開している
  • 起動時に同じ名前で CLMonitor を再初期化している

テスト:

  • バックグラウンドにすると、青いバックグラウンド位置情報インジケーターが表示される
  • アプリがバックグラウンドにある間も更新が継続する
  • アプリが一時停止されてから再開した後、更新が再開する
  • アプリが終了してから再起動した後、更新が再開する

パート 5:iOS バージョンに関する考慮事項

機能iOS バージョン備考
CLLocationUpdateiOS 17+AsyncSequence API
CLMonitoriOS 17+CLCircularRegion を置き換えます
CLBackgroundActivitySessioniOS 17+青いインジケーター付きのバックグラウンド動作
CLServiceSessioniOS 18+宣言的な認証
暗黙的なサービスセッションiOS 18+liveUpdates の反復処理から開始されます
CLLocationManageriOS 2+レガシーですが、引き続き動作します

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

コメント