RunWayホームへ
Under The Hood

4つの設計判断

並行処理、状態管理、端末間の同期、GPWS判定ロジック - RunWayを
作る上で最も悩んだ4つのポイントです。

CoreLocation
RunningCenter (actor · serial queue)
AsyncStream<FlightData>
HealthKit + WatchConnectivity
send / receive
ViewModel (@Observable) → SwiftUI
なぜこの設計にしたのか

“GPSの更新が連続的に発生し、そのたびにペース・距離・GPWS判定を再計算する必要があるため、この計算ロジックをRunningCenter Actorに隔離して状態の整合性を保証しました。心拍数とケイデンスはWatchConnectivityが受け取り、判定ロジックを持たずスレッドセーフに値を保持するだけのHealthCenter Actorに渡され、RunningCenterが必要なときにそこから値を取得して@Observable ViewModelへ渡します。”

SwiftUIUI
@MainActor + @Observable状態管理
RunningCenter (Actor)並行処理
HealthCenter (Actor)心拍数/ケイデンスのスレッドセーフな保管
AsyncStreamセンサー → ViewModel
FlightPhase enum状態機械
WatchConnectivityiPhone ↔ Watch
SwiftData保存
CloudKit (CKRecord)手動バックアップ/復元 · Private Database
Douglas-Peucker経路の対角線長に比例した誤差でGPS座標を圧縮
MapKit + MapPolylineルート可視化
HealthKit心拍数・ケイデンス・ワークアウトセッション
WidgetKit + ActivityKitDynamic Island
WeatherKitリアルタイム天気
AVSpeechSynthesizerkmスプリット・カウントダウンの音声案内
Combine位置情報・アラート・セッション状態のPublisher + Timer
System Flow

全体システムマップ

GPS・心拍・高度センサーから画面、保存、iPhone↔Watch同期まで、実際のアプリを
構成する17個のコンポーネントとその間を行き来するデータを一枚にまとめました。

ボックスをクリックすると右側に実際のコードが表示されます
01・センサー入力02・コア演算(actor)03・ViewModel・端末間同期04・リアルタイム画面05・保存06・記録閲覧LocationServiceSERVICEHealthCenterACTORAltimeterServiceSERVICERunningCenterACTORRunViewModelACTORWatchConnectivityServiceSERVICEWatchViewModelACTORPFDViewVIEWWatchGPWSViewVIEWWatchPFDViewVIEWSwiftData ModelsMODELCloudBackupServiceSERVICERunReminderServiceSERVICELogbookViewVIEWFlightSummaryViewVIEWAlertsViewVIEWFlightCalendarViewVIEW
RunningCenter
RunWay/Shared/Actor/RunningCenter.swift

すべてのランニング計算の中心。GPS/心拍/高度を受け取り、距離・ペース・GPWS・スプリットを計算してFlightDataストリームへ送り出す。

1actor RunningCenter {2    private(set) var totalDistance: Double = 03    private(set) var gpwsStatus: GPWSState = .normal4    private(set) var isStationary: Bool = false56    func processLocation(_ location: CLLocation) async {7        if location.speedAccuracy >= 0 {8        }9        continuation?.yield(flightData)10    }11}
担当機能
  • ▸累積距離の計算、二重指数移動平均(Double EMA)によるペーススムージング
  • ▸raw速度ベースの停止判定(isStationary)、speedAccuracyによる信頼度チェック
  • ▸GPWS(SINK RATE/OVERSPEED/MINIMUMS)判定、停止中はnormalへリセット
  • ▸km単位のスプリット記録、相対高度のスムージングと外れ値フィルタリング
なぜこの設計にしたのか

iOSとWatchはそれぞれ独立にGPS・心拍・高度を受け取るため、RunningCenterも端末ごとに別インスタンスとして動く。停止判定はGPS信頼度(speedAccuracy)が低いサンプルを除外しながら確定するが、当初はこのチェックが停止判定にしか掛かっておらず、ペース計算用の速度スムージングには掛かっていなかった。OVERSPEEDに反応して急減速する瞬間はまさに信頼度が負になりやすく、停止確定はできないままスムージング値だけが下がり続けてペースが際限なく膨らみ、アプリがクラッシュする実害につながった。スムージング更新を同じ信頼度チェックの中に移すことで1.3.1で解決した。