HealthKit 入门指南
Getting started with HealthKit
2020年6月23日
一句话判断
这是一场 HealthKit 的「从零到一」入门 Session——从权限请求到数据读写再到后台交付,把 HealthKit 的核心工作流用最清晰的方式串了一遍,如果你之前没碰过 HealthKit,看完这场就能动手了。
这场 Session 讲了什么
这场 Session 是 HealthKit 的入门教程,目标受众是完全没有 HealthKit 经验的开发者。内容涵盖了 HealthKit 的核心概念、数据模型、权限系统和基本的数据读写操作。
核心概念部分:HealthKit 是 Apple 设备上的健康数据中枢,所有健康相关数据(步数、心率、睡眠、血压等)都存储在 HealthKit 中。Apple Health app 是用户界面,HealthKit 是开发者的 API 层。
数据模型部分:HKObject 是所有健康数据的基类,HKQuantitySample 存储数值型数据(步数、心率),HKCategorySample 存储分类数据(睡眠分析),HKCorrelation 存储关联数据(一顿饭的食物+血压)。每个样本都有开始时间、结束时间、来源(哪个 app 写入的)。
权限系统:HealthKit 使用严格的权限模型,app 必须在 Info.plist 中声明需要读写的每种数据类型,用户逐项授权。隐私描述文字需要具体说明用途,不能写「需要健康数据」这种笼统的话。
值得深挖的点
后台交付(Background Delivery)的机制
HKObserverQuery 允许你的 app 在后台被唤醒,当指定类型的健康数据有新数据时。Apple Watch 采集的心率、步数等数据通过后台交付推送到 iOS app。后台唤醒的时间有限(大约 30 秒),你不能做复杂的计算,只能做简单的数据提取和本地通知。
HKSampleQuery 与 HKStatisticsQuery 的选择
HKSampleQuery 返回原始的样本数据(每条心率记录、每段步数记录)。HKStatisticsQuery 返回聚合统计(每天的平均心率、每周的总步数)。大多数健康类 app 应该优先用统计查询,除非你需要显示每条原始数据的图表。
代码片段
import HealthKit
// 第一步:检查设备是否支持 HealthKit
class HealthManager {
let healthStore = HKHealthStore()
func setup() {
guard HKHealthStore.isHealthDataAvailable() else {
print("此设备不支持 HealthKit")
return
}
requestAuthorization()
}
// 第二步:请求权限
func requestAuthorization() {
// 要读取的数据类型
let typesToRead: Set<HKObjectType> = [
HKObjectType.quantityType(forIdentifier: .stepCount)!,
HKObjectType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!
]
// 要写入的数据类型(通常是 app 自己产生的运动数据)
let typesToWrite: Set<HKSampleType> = [
HKObjectType.quantityType(forIdentifier: .stepCount)!
]
healthStore.requestAuthorization(toShare: typesToWrite, read: typesToRead) { success, error in
if success {
print("HealthKit 权限已授权")
self.queryTodaySteps()
}
}
}
// 第三步:查询今日步数
func queryTodaySteps() {
let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(
quantityType: stepType,
quantitySamplePredicate: predicate,
options: .cumulativeSum
) { _, result, error in
guard let result = result, let sum = result.sumQuantity() else { return }
let steps = sum.doubleValue(for: HKUnit.count())
print("今日步数: \(Int(steps))")
}
healthStore.execute(query)
}
}
// 写入运动数据到 HealthKit
func saveWorkout(distance: Double, duration: TimeInterval) {
let distanceType = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!
let distanceQuantity = HKQuantity(unit: HKUnit.meter(), doubleValue: distance)
let sample = HKQuantitySample(
type: distanceType,
quantity: distanceQuantity,
start: Date().addingTimeInterval(-duration),
end: Date()
)
healthStore.save(sample) { success, error in
if success {
print("运动数据已保存")
}
}
}
// 注册后台数据交付
func observeHeartRate() {
let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
let query = HKObserverQuery(sampleType: heartRateType, predicate: nil) { [weak self] _, completionHandler, error in
// 后台被唤醒,查询最新心率
self?.fetchLatestHeartRate()
// 必须调用 completionHandler
completionHandler()
}
healthStore.execute(query)
// 启用后台交付
healthStore.enableBackgroundDelivery(for: heartRateType, frequency: .immediate) { success, error in
if success {
print("心率后台交付已启用")
}
}
}
最佳实践
- 权限请求要分散到功能点。不要在 app 启动时一口气请求所有健康数据权限。用户在打开「心率」页面时才请求心率权限,打开「睡眠」页面时才请求睡眠权限。这样用户能理解每个权限的用途。
- Info.plist 的隐私描述要具体。
NSHealthShareUsageDescription不要写「需要读取健康数据」,要写「读取步数和心率来显示您的每日运动报告」。苹果会审核这段文字。 - 处理权限被拒绝的 UI。用户可能拒绝部分权限,你的 app 要能优雅降级。步数权限被拒绝了?显示「请在健康 app 中授权步数访问」的提示,而不是空白页面。
还有什么值得关注
- HealthKit 数据只在用户设备上,苹果看不到也不存储在云端(除非用户开启了 iCloud 健康同步)。
- 模拟器不支持 HealthKit,开发和测试必须用真机。
- iOS 14 新增了 Mobility 数据类型(详见 Session 10656),如果你做健康类 app 值得关注。