RORK LABEN
DEADLINE — Google Play の Android 16(API レベル36)必須化まで残り4日です。8月31日以降、新規アプリも既存アプリの更新も対象となりますRULES — 提出ルールと可視性ルールは別物です。更新を止めているアプリも、API 35 未満のままだと新しい端末で新規ユーザーに表示されなくなりますEXTENSION — 延長を申請すれば11月1日まで全ユーザーへの配信を続けられます。フォームは Play Console から出るため、締切前に済ませておく必要がありますEXPO — Expo SDK 57 は React Native を 0.85 から 0.86 へ引き上げ、React は 19.2 で据え置きです。0.86 は破壊的変更がない想定とされていますHERMES — 57.0.9 が React Native 0.86.2 に更新し、SDK 56 の Hermes V1 メモリ退行を解消しました。reanimated や worklets を使うアプリで効きますPREBUILD — expo prebuild が native の android・ios ディレクトリを既定でクリアして再生成します。手を入れた native 変更は、事前に洗い出さないと静かに失われますDEADLINE — Google Play の Android 16(API レベル36)必須化まで残り4日です。8月31日以降、新規アプリも既存アプリの更新も対象となりますRULES — 提出ルールと可視性ルールは別物です。更新を止めているアプリも、API 35 未満のままだと新しい端末で新規ユーザーに表示されなくなりますEXTENSION — 延長を申請すれば11月1日まで全ユーザーへの配信を続けられます。フォームは Play Console から出るため、締切前に済ませておく必要がありますEXPO — Expo SDK 57 は React Native を 0.85 から 0.86 へ引き上げ、React は 19.2 で据え置きです。0.86 は破壊的変更がない想定とされていますHERMES — 57.0.9 が React Native 0.86.2 に更新し、SDK 56 の Hermes V1 メモリ退行を解消しました。reanimated や worklets を使うアプリで効きますPREBUILD — expo prebuild が native の android・ios ディレクトリを既定でクリアして再生成します。手を入れた native 変更は、事前に洗い出さないと静かに失われます
記事一覧/開発ツール
開発ツール/2026-07-13上級

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

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

Rork Max232HealthKit5Swift47iOS109個人開発200

プレミアム記事

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

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/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-07-11
Rork Max で App Clip を実装する — QR を読んだ瞬間にアプリの核だけを届ける設計メモ
Rork Maxが出力するネイティブSwiftを土台に、App Clipを実装する設計メモです。15MB制約を守るターゲット分割、NSUserActivityでの起動URL受け取り、apple-app-site-associationの設定、親アプリへの状態引き継ぎまでを実装ベースで整理します。
開発ツール2026-07-04
Rork Max の健康アプリで、朝になると歩数が更新されない — HKObserverQuery が無言で止まる background delivery の設計
Rork Max が生成したネイティブ Swift の健康アプリで、アプリを閉じている間の歩数や心拍が反映されない——その多くは HKObserverQuery の background delivery が無言で止まっていることが原因でした。切り分けと、そのまま組み込める観測レイヤーの実装をまとめます。
開発ツール2026-07-18
AR で置いたものが翌朝には消えている — ARWorldMap による配置の永続化とリローカライズ設計
Rork Max が生成する AR アプリは、置いた 3D オブジェクトがアプリ再起動で消えます。ARWorldMap の保存タイミング、カスタムアンカーの符号化、リローカライズ待ちの見せ方、成立しない時の逃げ道までを設計として整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →