RORK LABEN
MAX — Rork MaxはReact NativeではなくネイティブSwiftアプリを生成し、iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — AR/LiDAR、Metalによる3D、ウィジェット、Dynamic Island、Live Activities、HealthKit、NFC、Core MLまで踏み込めますPUBLISH — App Storeへの公開を2クリックで実行でき、個人開発の申請作業を大きく短縮できますSWIFT — 自然文で指示するだけで動くコードが生成され、iOS・Android・Webのアプリを素早く形にできますGROWTH — Rorkは直近で1,500万ドルを調達。月間743,000訪問・成長率85%と伸びていますPRICING — 無料で開始でき、有料プランは月25ドルから。Rork Maxは月200ドルですMAX — Rork MaxはReact NativeではなくネイティブSwiftアプリを生成し、iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — AR/LiDAR、Metalによる3D、ウィジェット、Dynamic Island、Live Activities、HealthKit、NFC、Core MLまで踏み込めますPUBLISH — App Storeへの公開を2クリックで実行でき、個人開発の申請作業を大きく短縮できますSWIFT — 自然文で指示するだけで動くコードが生成され、iOS・Android・Webのアプリを素早く形にできますGROWTH — Rorkは直近で1,500万ドルを調達。月間743,000訪問・成長率85%と伸びていますPRICING — 無料で開始でき、有料プランは月25ドルから。Rork Maxは月200ドルです
記事一覧/開発ツール
開発ツール/2026-07-13上級

HealthKitの差分同期でデータを取りこぼす — HKAnchoredObjectQueryのアンカーを設計する

HealthKitの差分同期で歩数や睡眠が二重に数えられたり欠けたりする原因は、HKQueryAnchorの永続化設計にあります。newAnchorとdeletedObjectsを正しく扱い、再インストールやバックグラウンド更新をまたいでも整合するローカル取り込みの実装を、動くSwiftコードでまとめました。

Rork Max223HealthKit5Swift45iOS108個人開発183

プレミアム記事

睡眠と歩数を読み込む癒し系アプリに「昨日との差分だけ取り込む」処理を足した翌朝、テスト端末の週間グラフが二重になっていました。夜間のバックグラウンド更新と、朝アプリを開いたときの取得が、同じサンプルを二度数えていたのです。原因は取得方法ではなく、取得の続きをどこから始めるかを示す印、つまりアンカーの持ち方にありました。

HKSampleQuery で毎回すべてを取り直せば重複はしませんが、健康データは1日で数百件に増えることもあり、起動のたびに全件を舐めるのは電池にも速度にも優しくありません。そこで差分取得の HKAnchoredObjectQuery に切り替えるのですが、この API は「返ってきたアンカーを次回に渡す」という約束を守って初めて正しく動きます。約束の破り方には典型的なパターンがあり、私自身も個人開発でひととおり踏みました。ここではその設計を、動くコードとともに整理します。

なぜ全件取得をやめると事故が起きるのか

HKAnchoredObjectQuery は「前回のアンカー以降に追加・削除されたサンプルだけ」を返します。裏を返せば、アンカーを渡さなければ毎回すべてが「新規」として返ってきます。ここで多いのが、アンカーをメモリ上の変数にだけ持ち、アプリを再起動すると nil に戻る作りです。すると再起動のたびに全件が新規扱いになり、ローカル側で素朴に加算していれば二重計上、置換していれば無駄な全書き換えが起きます。

もう一つの落とし穴が削除です。ユーザーがヘルスケアアプリで手入力の歩数を消しても、差分取得を歩数の追加だけで組んでいると、ローカルには古い値が残り続けます。差分同期は「増えた分」と「消えた分」の両方を受け取って初めて、ヘルスケア側と一致します。

取得方法毎回のコスト削除の反映アンカー管理
HKSampleQuery で全件高い(件数に比例)全置換なら反映される不要
HKAnchoredObjectQuery(アンカー未永続化)実質全件(毎回新規扱い)初回のみ不完全
HKAnchoredObjectQuery(アンカー永続化)低い(差分のみ)deletedObjectsで反映必須

つまり差分同期の正しさは、ほぼアンカーの持ち方で決まります。

HKAnchoredObjectQuery が返す3つの値を扱う

このクエリの結果ハンドラには、追加されたサンプル・削除されたオブジェクト・新しいアンカーの3つが渡されます。まずこの3つを受け取り、ローカル取り込みへ渡す骨組みを作ります。

import HealthKit
 
final class StepSyncEngine {
    private let store = HKHealthStore()
    private let type = HKQuantityType(.stepCount)
 
    // 前回の続きから取り込む。初回は anchor が nil。
    func syncIncremental(completion: @escaping (Result<Void, Error>) -> Void) {
        let anchor = AnchorStore.load(for: type)  // 永続化から復元(後述)
 
        let query = HKAnchoredObjectQuery(
            type: type,
            predicate: nil,
            anchor: anchor,
            limit: HKObjectQueryNoLimit
        ) { [weak self] _, newSamples, deletedObjects, newAnchor, error in
            guard let self else { return }
            if let error { completion(.failure(error)); return }
 
            // 3つを1つの取り込み単位として渡す(順序が大事)
            self.ingest(
                added: (newSamples as? [HKQuantitySample]) ?? [],
                deleted: deletedObjects ?? []
            )
 
            // 取り込みが成功した「後で」アンカーを保存する
            if let newAnchor {
                AnchorStore.save(newAnchor, for: self.type)
            }
            completion(.success(()))
        }
        store.execute(query)
    }
}

ここで意図的にしているのは、アンカーの保存を取り込みの後に置くことです。取り込みが例外で失敗したのにアンカーだけ進めてしまうと、その差分は二度と返ってきません。「取り込めた分までしかアンカーを進めない」——この順序が差分同期の安全弁になります。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
差分同期で歩数や睡眠が二重計上・取りこぼしになる原因を、アンカーの永続化設計から根本的に直せるようになる
HKAnchoredObjectQueryのnewAnchorとdeletedObjectsを正しく扱う、動くSwiftコードを手に入れられる
再インストールやバックグラウンド更新をまたいでも数字がずれない、冪等なローカル取り込み設計に応用できる
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-07-11
Rork Max で App Clip を実装する — QR を読んだ瞬間にアプリの核だけを届ける設計メモ
Rork Max が出力するネイティブ Swift を土台に、App Clip の15MB制約・起動URLの受け取り・親アプリへの状態引き継ぎまでを実装ベースで整理します。
開発ツール2026-07-04
Rork Max の健康アプリで、朝になると歩数が更新されない — HKObserverQuery が無言で止まる background delivery の設計
Rork Max が生成したネイティブ Swift の健康アプリで、アプリを閉じている間の歩数や心拍が反映されない——その多くは HKObserverQuery の background delivery が無言で止まっていることが原因でした。切り分けと、そのまま組み込める観測レイヤーの実装をまとめます。
開発ツール2026-07-10
クラッシュとして記録されない強制終了 — Rork アプリの JetsamEvent を読む
Crashlytics には何も出ていないのにレビューで「勝手に閉じる」と書かれる。その多くはメモリ超過による OS からの強制終了です。JetsamEvent レポートの読み方と、画像中心アプリのメモリ上限を実測から設計する手順をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →