Highlight
HealthKit 用 HKAnchoredObjectQuery 监测增删变化,并用 HKMetadataKeySyncIdentifier 与 HKMetadataKeySyncVersion 让外部服务器回写的健康样本在多设备之间去重和更新。
核心内容
这场 session 从一个康复 App 开始。病人每天用 iPhone 和 Apple Watch 记录步数,物理治疗师每周录入六分钟步行测试结果。病人关心趋势,治疗师需要看到同一份数据,App 还要把图表同步到服务器。
如果只靠 HKStatisticsCollectionQuery 定时刷新,App 要在启动时或每隔几小时重新计算整周步数。健康数据没有变化时,这会重复计算;只有一天有新步数时,它仍然可能把整周结果发到服务器。健康数据越久,浪费越明显。
Apple 给出的做法是把查询拆成两层。HKAnchoredObjectQuery 负责告诉 App HealthKit 里哪些样本新增或删除,HKStatisticsCollectionQuery 只重算受影响日期的统计结果。服务器拿到的是变化后的日统计,而不是整批历史数据。
反方向同步更敏感。治疗师服务器里的六分钟步行测试要保存进病人的 HealthKit;同一份周报可能先到 iPhone,再通过 HealthKit 同步到 Apple Watch。App 不能重复写入,也不能随意删除用户健康数据。HKMetadataKeySyncIdentifier 和 HKMetadataKeySyncVersion 把这件事交给 HealthKit 做版本判断。
详细内容
只用统计查询会浪费网络和计算
(02:56)步数图表应该先想到 HKStatisticsCollectionQuery。它适合按天汇总步数,也适合做康复趋势图。问题出在同步场景:如果 App 用固定频率重跑统计查询,它不知道 HealthKit 里到底哪一天变了。
旧流程
1. HKStatisticsCollectionQuery 计算一周步数图表
2. App 启动或定时再次执行查询
3. 每次把整周统计结果发送到服务器
改造目标
1. 先找出 HealthKit 中发生变化的样本
2. 只重算这些样本影响到的日期
3. 只把更新后的日期结果发送到外部数据存储
关键点:
HKStatisticsCollectionQuery负责生成图表,不负责判断数据是否变更。- 固定频率刷新会产生重复计算,还会把没有变化的数据反复传给服务器。
- 外部数据存储可以是远端服务器,也可以是本机的 Core Data 或 SQLite。
Anchor 让 App 只拿到变化集
(04:32)HKAnchoredObjectQuery 的核心是 anchor。第一次查询时 anchor 为 nil,HealthKit 返回当前匹配的全部样本,并在 handler 中给出新的 anchor。下一次查询传入保存下来的 anchor,HealthKit 只返回这之后新增的样本和删除的样本。
第一次查询
anchor = nil
HealthKit 当前样本 = A, B, C
handler 收到 = A, B, C
保存 newAnchor
后续查询
HealthKit 新增 = D, E
HealthKit 删除 = B
使用 persistedAnchor 再次查询
handler 收到 = D, E, deleted B
再次保存 newAnchor
关键点:
- anchor 表示 HealthKit 数据库演进到某个时间点的位置。
- handler 会收到新增样本,也会收到删除样本。
- 保存 anchor 后,App 不需要每次扫描 A、C 这类没有变化的旧样本。
两个查询配合:先定位变化,再重算统计
(07:18)这场演讲没有提供官方 code snippets,但 transcript 给出了实现顺序:anchored query 使用步数 sample type 和持久化 anchor;handler 根据返回样本的日期创建 predicate;随后用这个 predicate 创建 statistics collection query,按天计算累计步数。
HKAnchoredObjectQuery
sampleType = step count
predicate = nil
anchor = persistedAnchor
initialResultsHandler = handler
updateHandler = handler
handler
1. 读取 HealthKit 返回的 samples
2. 从 samples 的日期生成 predicate
3. 保存 HealthKit 返回的新 anchor
4. 调用 fetchStatistics(predicate)
fetchStatistics(predicate)
1. anchorDate = Monday midnight
2. interval = one day
3. sampleType = step count
4. options = cumulativeSum
5. HKStatisticsCollectionQuery 使用上一步 predicate
关键点:
initialResultsHandler和updateHandler可以复用同一个处理块,因为两者都要把变化映射成统计刷新。- predicate 来自 anchored query 返回样本的日期范围,用来限制 statistics query 的计算范围。
cumulativeSum对应“每天总步数”这个图表目标。- 执行 query 的入口仍然是
healthStore。
六分钟步行测试要按样本保存
(10:24)2020 年 HealthKit 新增了一组 mobility data types,其中包括 sixMinuteWalkTestDistance。步数适合按天汇总,六分钟步行测试频率较低,病人和治疗师可能要查看每一次测试结果,所以这里应该把周报里的每条测试保存成单独样本。
服务器周报
1. 读取 weekly report
2. 遍历 weekly report 中的每条 serverHealthSample
3. 创建 HKQuantitySample
- quantity 使用 meter 单位
- value 来自 serverHealthSample
- sampleType = sixMinuteWalkTestDistance
- startDate / endDate 来自测试记录
4. healthStore 保存这些 samples
关键点:
- 同一种 App 里,不同 HealthKit 数据类型可以采用不同同步策略。
- 步数样本量大,常见目标是图表统计。
- 六分钟步行测试样本少,单条记录本身有诊疗意义。
- 删除样本前要先查询确认它由自己的 App 写入,并且删除必须符合用户意图。
Sync identifier 和 version 负责去重与更新
(13:54)外部服务器更新一条测试记录时,App 不能简单地再保存一次。HealthKit 提供两个 metadata key:HKMetadataKeySyncIdentifier 是字符串,用来在健康生态里识别同一条样本;HKMetadataKeySyncVersion 是数字,用来判断样本是否更新。
保存服务器样本
metadata[HKMetadataKeySyncIdentifier] = serverHealthSample.syncIdentifier
metadata[HKMetadataKeySyncVersion] = serverHealthSample.syncVersion
HKQuantitySample 使用这份 metadata
healthStore 保存 sample
HealthKit 行为
同一 syncIdentifier + 同一 version 再次写入 -> 忽略重复样本
同一 syncIdentifier + 更高 version 写入 -> 用新样本覆盖旧样本
基于 sync identifier 的操作 -> 保持事务安全
关键点:
- identifier 必须来自外部数据源的稳定样本标识,不能每次生成新值。
- version 增加时,HealthKit 才会把它当成对同一样本的更新。
- 同一份周报同步到 iPhone 后,再同步到 Apple Watch,HealthKit 会识别重复写入。
- 只要 App 维护好 identifier 和 version,冲突解决和多设备一致性由 HealthKit 处理。
核心启发
- 康复周报同步:做什么:把治疗师服务器里的六分钟步行测试周报同步到病人的 App。为什么值得做:session 明确使用
sixMinuteWalkTestDistance建模这种测试。怎么开始:把服务器样本映射成HKQuantitySample,用HKMetadataKeySyncIdentifier和HKMetadataKeySyncVersion写入 metadata。 - 步数趋势共享:做什么:让病人授权后,把每天总步数趋势同步给护理团队。为什么值得做:
HKAnchoredObjectQuery可以只监听变化样本,避免每次上传整周数据。怎么开始:先用 anchored query 找到变化日期,再用HKStatisticsCollectionQuery计算这些日期的cumulativeSum。 - 本地缓存对账:做什么:为 HealthKit 数据建立 Core Data 或 SQLite 缓存,只在健康数据变化时更新缓存。为什么值得做:transcript 把外部数据存储扩展到本机数据库。怎么开始:持久化 HealthKit anchor,把 handler 返回的新增和删除样本转成缓存增删改。
- 重复导入保护:做什么:给来自医院、教练或第三方设备的健康样本加上稳定外部 ID。为什么值得做:同一记录可能从 iPhone 和 Apple Watch 多次进入 HealthKit。怎么开始:把服务器记录 ID 写入
HKMetadataKeySyncIdentifier,把服务器版本号写入HKMetadataKeySyncVersion。
关联 Session
- Getting started with HealthKit — 这场同步演讲直接沿用 SmoothWalker 示例,前置内容讲 HealthKit 授权、读写和基础查询。
- Beyond counting steps — 继续展开 mobility metrics,适合接在步数和六分钟步行测试之后阅读。
- What’s new in HealthKit — 汇总 2020 年 HealthKit 新增能力,包括 ECG、症状记录和 mobility data types。
- What’s new in CareKit — session 结尾推荐 CareKit,用来构建护理场景中的图表和任务体验。
评论
GitHub Issues · utterances