MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift健康与健身数据访问

2023-04-056.4k 阅读

1. 健康与健身数据框架简介

在iOS开发中,HealthKit框架是用于访问和分享健康与健身数据的核心组件。它提供了一个统一的接口,允许开发者与用户设备上存储的健康数据进行交互。HealthKit支持多种类型的数据,包括步数、心率、体重、睡眠数据等等。通过HealthKit,应用程序可以读取这些数据以提供有价值的分析和展示,也可以写入数据以记录用户的健康活动。

1.1 HealthKit的架构

HealthKit的架构围绕着Health Store展开,Health Store是所有健康数据的中央仓库。应用程序通过HKHealthStore类与Health Store进行交互。这个类提供了一系列的方法,用于请求权限、读取数据、写入数据以及订阅数据更新。

当应用程序想要访问健康数据时,首先需要请求用户的授权。这是因为健康数据涉及用户的隐私,必须经过明确的同意才能访问。一旦获得授权,应用程序就可以使用HKHealthStore的方法来查询特定类型的数据。例如,查询用户当天的步数数据,或者写入新的锻炼记录。

1.2 数据类型

HealthKit定义了丰富的数据类型,每种类型都有其对应的类。常见的数据类型包括:

  • HKQuantityType:用于表示可测量的数据,如体重、身高、心率等。这些数据通过HKQuantity对象来表示具体的值。例如,体重可以用HKQuantity表示为150 pounds,其中150是数值,pounds是单位。
  • HKCategoryType:用于表示分类数据,比如睡眠状态(清醒、浅睡、深睡)。这些数据通过HKCategoryValue对象来表示具体的类别。
  • HKCharacteristicType:用于表示用户的特征数据,如出生日期、血型等。

2. 在Swift中使用HealthKit

2.1 导入HealthKit框架

在Swift项目中使用HealthKit,首先要在源文件中导入HealthKit框架:

import HealthKit

2.2 初始化Health Store

初始化HKHealthStore对象是与HealthKit交互的第一步:

let healthStore = HKHealthStore()

这个对象是整个健康数据访问的入口,后续的权限请求、数据读取和写入操作都通过它来完成。

2.3 请求授权

在访问任何健康数据之前,应用程序必须请求用户的授权。这涉及到创建一个Set对象,包含应用程序想要访问的所有数据类型。

2.3.1 读取权限请求

假设应用程序想要读取步数数据,代码如下:

// 1. 创建步数类型对象
let stepType = HKObjectType.quantityType(forIdentifier:.stepCount)!
// 2. 创建想要读取的数据类型集合
let readTypes: Set<HKObjectType> = [stepType]
// 3. 请求授权
healthStore.requestAuthorization(toShare: nil, read: readTypes) { (success, error) in
    if success {
        print("授权成功")
    } else {
        print("授权失败: \(error?.localizedDescription ?? "未知错误")")
    }
}

在上述代码中,首先通过HKObjectType.quantityType(forIdentifier:)方法获取步数数据的类型对象。然后创建一个包含该类型的Set,用于传递给requestAuthorization(toShare:read:)方法。该方法的第一个参数toShare用于设置应用程序想要写入的数据类型,这里设置为nil,因为只请求读取权限。如果授权成功,successtrue,否则为false,并可以通过error获取失败原因。

2.3.2 写入权限请求

如果应用程序需要写入数据,例如记录一次锻炼,代码如下:

// 1. 创建锻炼类型对象
let workoutType = HKObjectType.workoutType()
// 2. 创建想要写入的数据类型集合
let writeTypes: Set<HKObjectType> = [workoutType]
// 3. 请求授权
healthStore.requestAuthorization(toShare: writeTypes, read: nil) { (success, error) in
    if success {
        print("授权成功")
    } else {
        print("授权失败: \(error?.localizedDescription ?? "未知错误")")
    }
}

这里获取锻炼数据类型对象HKObjectType.workoutType(),并将其放入Set中传递给requestAuthorization(toShare:read:)方法,这次toShare参数设置为写入类型集合,read设置为nil,因为只请求写入权限。

3. 读取健康与健身数据

3.1 查询步数数据

3.1.1 创建查询

在获得读取步数数据的授权后,可以创建一个查询来获取数据。下面的代码展示了如何查询当天的步数:

// 1. 创建步数类型对象
let stepType = HKObjectType.quantityType(forIdentifier:.stepCount)!
// 2. 创建日期组件,用于设置查询的起始和结束时间
let calendar = Calendar.current
let now = Date()
let startOfDay = calendar.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options:.strictStartDate)
// 3. 创建查询对象
let query = HKStatisticsQuery(quantityType: stepType,
                              quantitySamplePredicate: predicate,
                              options:.cumulativeSum) { (query, result, error) in
    if let result = result, let sum = result.sumQuantity() {
        let stepCount = sum.doubleValue(for: HKUnit.count())
        print("当天步数: \(stepCount)")
    } else {
        print("查询失败: \(error?.localizedDescription ?? "未知错误")")
    }
}
// 4. 执行查询
healthStore.execute(query)

在这段代码中,首先获取步数数据类型对象stepType。然后通过Calendar获取当天的起始时间startOfDay,并创建一个谓词predicate,用于限定查询的时间范围。接着创建一个HKStatisticsQuery对象,该对象用于统计数据。这里使用.cumulativeSum选项来获取步数的总和。在查询的完成闭包中,如果查询成功,从结果中获取步数总和并打印;如果失败,打印错误信息。最后通过healthStore.execute(query)执行查询。

3.1.2 处理查询结果

查询结果通过完成闭包返回。在上述代码中,HKStatisticsQuery的完成闭包参数resultHKStatistics类型。如果查询成功,result不为nil,可以通过sumQuantity()方法获取总和。sumQuantity()返回一个HKQuantity对象,通过doubleValue(for:)方法可以将其转换为指定单位下的Double值。这里使用HKUnit.count()表示步数的单位。

3.2 查询心率数据

3.2.1 创建心率查询

心率数据也是HKQuantityType类型,下面的代码展示了如何查询最近一小时的心率数据:

// 1. 创建心率类型对象
let heartRateType = HKObjectType.quantityType(forIdentifier:.heartRate)!
// 2. 创建日期组件,设置查询的起始和结束时间
let calendar = Calendar.current
let now = Date()
let oneHourAgo = calendar.date(byAdding:.hour, value: -1, to: now)!
let predicate = HKQuery.predicateForSamples(withStart: oneHourAgo, end: now, options:.strictStartDate)
// 3. 创建查询对象
let query = HKAnchoredObjectQuery(type: heartRateType,
                                  predicate: predicate,
                                  anchor: nil,
                                  limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, newAnchor, error) in
    if let samples = samples {
        for sample in samples {
            if let heartRateSample = sample as? HKQuantitySample {
                let heartRate = heartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min"))
                print("心率: \(heartRate) 次/分钟")
            }
        }
    } else {
        print("查询失败: \(error?.localizedDescription ?? "未知错误")")
    }
}
// 4. 执行查询
healthStore.execute(query)

在这段代码中,首先获取心率数据类型对象heartRateType。然后通过Calendar计算出一小时前的时间oneHourAgo,并创建一个谓词predicate来限定查询的时间范围。接着创建一个HKAnchoredObjectQuery对象,该对象用于获取一段时间内的样本数据。在完成闭包中,如果查询成功,遍历返回的样本数据,将其转换为HKQuantitySample类型,然后获取心率值并打印。最后通过healthStore.execute(query)执行查询。

3.2.2 理解HKAnchoredObjectQuery

HKAnchoredObjectQuery可以获取一段时间内的样本数据,并提供了增量更新的功能。anchor参数用于设置查询的起始点,如果设置为nil,则从最早的数据开始查询。limit参数设置返回的最大样本数量,HKObjectQueryNoLimit表示不限制数量。在完成闭包中,samples包含查询到的样本数据,deletedObjects包含在查询时间范围内被删除的样本数据,newAnchor可以用于下次查询,以实现增量更新。

4. 写入健康与健身数据

4.1 记录锻炼数据

4.1.1 创建锻炼对象

记录一次锻炼需要创建一个HKWorkout对象。下面的代码展示了如何创建一次跑步锻炼记录:

// 1. 创建锻炼类型对象
let workoutType = HKWorkoutType.workoutType()
// 2. 创建锻炼配置
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType =.running
workoutConfiguration.locationType =.outdoor
// 3. 创建锻炼对象
let startDate = Date()
let endDate = calendar.date(byAdding:.minute, value: 30, to: startDate)!
let workout = try? HKWorkout(activityType: workoutConfiguration.activityType,
                              start: startDate,
                              end: endDate,
                              duration: endDate.timeIntervalSince(startDate),
                              totalEnergyBurned: nil,
                              totalDistance: nil,
                              metadata: nil,
                              device: nil,
                              workoutEvents: nil,
                              workoutConfiguration: workoutConfiguration)

在这段代码中,首先获取锻炼类型对象workoutType。然后创建一个HKWorkoutConfiguration对象,设置锻炼的类型为跑步,位置类型为户外。接着通过提供的锻炼配置、开始时间、结束时间等信息创建HKWorkout对象。这里假设锻炼持续30分钟。

4.1.2 写入锻炼数据

创建好锻炼对象后,可以将其写入Health Store:

if let workout = workout {
    healthStore.save(workout) { (success, error) in
        if success {
            print("锻炼记录保存成功")
        } else {
            print("锻炼记录保存失败: \(error?.localizedDescription ?? "未知错误")")
        }
    }
}

这里通过healthStore.save(workout)方法将锻炼对象保存到Health Store。如果保存成功,successtrue,否则为false,并可以通过error获取失败原因。

4.2 记录体重数据

4.2.1 创建体重样本

记录体重数据需要创建一个HKQuantitySample对象。下面的代码展示了如何创建并记录一次体重数据:

// 1. 创建体重类型对象
let weightType = HKObjectType.quantityType(forIdentifier:.bodyMass)!
// 2. 创建体重值和单位
let weightValue = 70.0
let weightUnit = HKUnit.gramUnit(with: .kilo)
let quantity = HKQuantity(unit: weightUnit, doubleValue: weightValue)
// 3. 创建体重样本
let now = Date()
let weightSample = HKQuantitySample(type: weightType, quantity: quantity, start: now, end: now)

在这段代码中,首先获取体重数据类型对象weightType。然后创建体重值weightValue为70千克,并使用HKUnit.gramUnit(with:.kilo)表示单位为千克。接着通过体重值和单位创建HKQuantity对象quantity。最后使用当前时间作为起始和结束时间,创建HKQuantitySample对象weightSample

4.2.2 写入体重样本

创建好体重样本后,可以将其写入Health Store:

healthStore.save(weightSample) { (success, error) in
    if success {
        print("体重数据保存成功")
    } else {
        print("体重数据保存失败: \(error?.localizedDescription ?? "未知错误")")
    }
}

通过healthStore.save(weightSample)方法将体重样本保存到Health Store。如果保存成功,successtrue,否则为false,并可以通过error获取失败原因。

5. 处理数据更新

5.1 使用HKObserverQuery监听数据变化

HKObserverQuery可以用于监听特定类型数据的变化。例如,监听步数数据的变化,代码如下:

// 1. 创建步数类型对象
let stepType = HKObjectType.quantityType(forIdentifier:.stepCount)!
// 2. 创建查询对象
let query = HKObserverQuery(sampleType: stepType, predicate: nil) { (query, completionHandler, error) in
    if let error = error {
        print("监听失败: \(error.localizedDescription)")
    } else {
        print("步数数据发生变化")
        // 这里可以进行新数据的查询操作
        completionHandler()
    }
}
// 3. 执行查询
healthStore.execute(query)

在这段代码中,首先获取步数数据类型对象stepType。然后创建一个HKObserverQuery对象,当步数数据发生变化时,会调用完成闭包。在闭包中,如果发生错误,打印错误信息;如果没有错误,打印提示信息,并调用completionHandler()方法通知HealthKit已经处理了变化。这样可以在数据变化时及时进行相应的操作,比如重新查询最新的步数数据。

5.2 增量更新数据

通过HKAnchoredObjectQuery可以实现增量更新数据。在之前查询心率数据的代码中,newAnchor参数可以用于下次查询。例如:

var anchor: HKQueryAnchor?
// 第一次查询
let query = HKAnchoredObjectQuery(type: heartRateType,
                                  predicate: predicate,
                                  anchor: anchor,
                                  limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, newAnchor, error) in
    if let newAnchor = newAnchor {
        anchor = newAnchor
    }
    // 处理查询结果
}
healthStore.execute(query)
// 后续增量查询
let incrementalQuery = HKAnchoredObjectQuery(type: heartRateType,
                                             predicate: predicate,
                                             anchor: anchor,
                                             limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, newAnchor, error) in
    if let newAnchor = newAnchor {
        anchor = newAnchor
    }
    // 处理增量查询结果
}
healthStore.execute(incrementalQuery)

在第一次查询时,anchornil,查询所有符合条件的数据。在完成闭包中,如果有新的HKQueryAnchor对象newAnchor,则保存下来。后续进行增量查询时,将保存的anchor传递给HKAnchoredObjectQuery,这样就只会获取从上次查询之后新增或修改的数据,从而实现增量更新。

6. 错误处理与最佳实践

6.1 错误处理

在与HealthKit交互的过程中,可能会遇到各种错误。例如,授权失败、查询失败、保存失败等。在前面的代码示例中,已经展示了如何通过error参数获取错误信息并进行相应的处理。

常见的错误类型包括:

  • 权限相关错误:如用户拒绝授权,会返回相应的权限错误。在这种情况下,应用程序可以提示用户打开设置以授予权限。
  • 数据类型错误:如果请求的健康数据类型不存在,会返回数据类型错误。这通常是由于代码中使用了错误的标识符或HealthKit版本不支持该类型导致的。

6.2 最佳实践

  • 明确权限请求:在请求用户授权时,清楚地说明应用程序需要访问哪些数据以及为什么需要这些数据。这有助于提高用户的信任度并获得授权。
  • 数据验证:在写入数据之前,对数据进行验证,确保数据的准确性和完整性。例如,体重数据应该在合理的范围内。
  • 定期更新数据:对于需要实时展示的数据,如心率、步数等,使用数据更新机制(如HKObserverQuery)定期获取最新数据,以提供准确的信息给用户。
  • 保护用户隐私:始终遵循苹果的隐私政策,妥善处理用户的健康数据。不要将用户的健康数据泄露给第三方,除非获得明确的授权。

通过以上对Swift中HealthKit健康与健身数据访问的详细介绍,开发者可以在自己的应用程序中充分利用这一强大的框架,为用户提供丰富的健康与健身相关功能。无论是开发健身追踪应用、健康管理应用还是其他与健康相关的应用,掌握HealthKit的使用都是至关重要的。