WWDC Quick Look 💓 By SwiftGGTeam
Synchronize health data with HealthKit

Synchronize health data with HealthKit

观看原视频

Highlight

HealthKit 用 HKAnchoredObjectQuery 监测增删变化,并用 HKMetadataKeySyncIdentifier 与 HKMetadataKeySyncVersion 让外部服务器回写的健康样本在多设备之间去重和更新。

核心内容

这场 session 从一个康复 App 开始。病人每天用 iPhone 和 Apple Watch 记录步数,物理治疗师每周录入六分钟步行测试结果。病人关心趋势,治疗师需要看到同一份数据,App 还要把图表同步到服务器。

如果只靠 HKStatisticsCollectionQuery 定时刷新,App 要在启动时或每隔几小时重新计算整周步数。健康数据没有变化时,这会重复计算;只有一天有新步数时,它仍然可能把整周结果发到服务器。健康数据越久,浪费越明显。

Apple 给出的做法是把查询拆成两层。HKAnchoredObjectQuery 负责告诉 App HealthKit 里哪些样本新增或删除,HKStatisticsCollectionQuery 只重算受影响日期的统计结果。服务器拿到的是变化后的日统计,而不是整批历史数据。

反方向同步更敏感。治疗师服务器里的六分钟步行测试要保存进病人的 HealthKit;同一份周报可能先到 iPhone,再通过 HealthKit 同步到 Apple Watch。App 不能重复写入,也不能随意删除用户健康数据。HKMetadataKeySyncIdentifierHKMetadataKeySyncVersion 把这件事交给 HealthKit 做版本判断。

详细内容

只用统计查询会浪费网络和计算

02:56)步数图表应该先想到 HKStatisticsCollectionQuery。它适合按天汇总步数,也适合做康复趋势图。问题出在同步场景:如果 App 用固定频率重跑统计查询,它不知道 HealthKit 里到底哪一天变了。

旧流程
1. HKStatisticsCollectionQuery 计算一周步数图表
2. App 启动或定时再次执行查询
3. 每次把整周统计结果发送到服务器

改造目标
1. 先找出 HealthKit 中发生变化的样本
2. 只重算这些样本影响到的日期
3. 只把更新后的日期结果发送到外部数据存储

关键点:

  • HKStatisticsCollectionQuery 负责生成图表,不负责判断数据是否变更。
  • 固定频率刷新会产生重复计算,还会把没有变化的数据反复传给服务器。
  • 外部数据存储可以是远端服务器,也可以是本机的 Core Data 或 SQLite。

Anchor 让 App 只拿到变化集

04:32HKAnchoredObjectQuery 的核心是 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

关键点:

  • initialResultsHandlerupdateHandler 可以复用同一个处理块,因为两者都要把变化映射成统计刷新。
  • 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,用 HKMetadataKeySyncIdentifierHKMetadataKeySyncVersion 写入 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