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

Objective-C中的HealthKit健康数据访问

2024-10-234.1k 阅读

HealthKit 简介

HealthKit 是苹果公司为 iOS 设备推出的健康数据管理框架,它允许开发者创建能够与用户健康数据交互的应用程序。通过 HealthKit,应用可以读取和写入各种健康数据,如步数、心率、睡眠数据等。这一框架为健康类应用的开发提供了统一且安全的方式来访问和管理用户敏感的健康信息。

HealthKit 的重要性

  1. 数据整合:HealthKit 可以整合来自不同健康和健身应用的数据。例如,一个跑步应用记录的跑步距离数据,以及一个心率监测应用记录的心率数据,都可以通过 HealthKit 集中管理。这使得用户能够在一个地方查看自己全面的健康数据,也为开发者提供了更丰富的数据来源,用于创建更强大的健康分析功能。
  2. 隐私保护:由于健康数据的敏感性,隐私保护至关重要。HealthKit 遵循严格的隐私政策,用户对哪些应用可以访问自己的健康数据有完全的控制权。应用在访问数据前必须明确请求用户授权,并且 HealthKit 会对数据进行加密存储,确保数据的安全性。

在 Objective-C 项目中引入 HealthKit

1. 配置项目

  1. 检查设备支持:首先,要确保你的应用运行的设备支持 HealthKit。并非所有的 iOS 设备都具备收集所有类型健康数据的硬件能力。例如,较老的设备可能没有心率传感器。你可以通过以下代码检查设备是否支持 HealthKit:
if (![HKHealthStore isHealthDataAvailable]) {
    // 设备不支持 HealthKit
    NSLog(@"This device does not support HealthKit.");
    return;
}
  1. 添加框架引用:在 Xcode 项目中,导航到项目设置,选择“General”标签,在“Embedded Binaries”和“Linked Frameworks and Libraries”部分,点击“+”按钮并添加“HealthKit.framework”。这将确保你的项目能够使用 HealthKit 的类和方法。

2. 创建 HealthKit 存储实例

在 Objective-C 中,与 HealthKit 交互的核心是 HKHealthStore 类。你需要创建一个该类的实例来进行数据的读取和写入操作。通常,你可以在应用的视图控制器或应用委托中创建这个实例。

#import <HealthKit/HealthKit.h>

@interface ViewController ()
@property (nonatomic, strong) HKHealthStore *healthStore;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.healthStore = [[HKHealthStore alloc] init];
}

请求健康数据访问权限

1. 确定要访问的数据类型

HealthKit 支持多种数据类型,包括但不限于:

  1. 步数数据HKQuantityTypeIdentifierStepCount
  2. 心率数据HKQuantityTypeIdentifierHeartRate
  3. 睡眠分析数据HKCategoryTypeIdentifierSleepAnalysis

你需要明确指定你的应用想要访问哪些数据类型。例如,如果你想读取用户的步数和心率数据,可以这样定义数据类型集合:

// 定义要读取的数量类型
NSSet<HKObjectType *> *readTypes = [NSSet setWithObjects:
    [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],
    [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate],
    nil];

// 定义要写入的数量类型(如果需要写入)
NSSet<HKObjectType *> *writeTypes = [NSSet setWithObjects:
    [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],
    nil];

2. 请求授权

一旦确定了要访问的数据类型,就可以向用户请求授权。这是通过 HKHealthStorerequestAuthorizationToShareTypes:readTypes:completion: 方法来完成的。这个方法是异步的,它会弹出一个系统对话框,让用户决定是否授予你的应用访问权限。

[self.healthStore requestAuthorizationToShareTypes:writeTypes readTypes:readTypes completion:^(BOOL success, NSError * _Nullable error) {
    if (!success || error) {
        if (error) {
            NSLog(@"HealthKit authorization error: %@", error);
        }
        // 处理授权失败的情况
        return;
    }
    // 授权成功,开始进行数据访问操作
    NSLog(@"HealthKit authorization granted.");
}];

读取健康数据

1. 读取步数数据

  1. 创建查询:要读取步数数据,首先需要创建一个 HKQuery。对于步数这样的数量类型数据,通常使用 HKStatisticsQuery。这个查询可以统计在指定时间段内的步数总数。
// 获取步数数量类型
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

// 设置查询时间段
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow: - 24 * 60 * 60]; // 过去 24 小时
NSDate *endDate = [NSDate date];
HKDateComponents *intervalComponents = [[HKDateComponents alloc] init];
intervalComponents.day = 1;

// 创建统计查询
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:stepType
                                                                          quantitySamplePredicate:nil
                                                                                  options:HKStatisticsOptionDiscreteAverage
                                                                            anchorDate:startDate
                                                                         intervalComponents:intervalComponents];
  1. 执行查询:创建好查询后,通过 HKHealthStoreexecuteQuery: 方法来执行查询。查询结果会在完成块中返回。
__weak typeof(self) weakSelf = self;
[query setCompletionHandler:^(HKStatisticsCollectionQuery * _Nonnull query, HKStatisticsCollection * _Nullable result, NSError * _Nullable error) {
    if (error) {
        NSLog(@"Step count query error: %@", error);
        return;
    }

    // 获取统计结果
    [result enumerateStatisticsFromDate:startDate toDate:endDate withBlock:^(HKStatistics * _Nonnull result, BOOL * _Nonnull stop) {
        HKQuantity *totalSteps = result.sumQuantity;
        if (totalSteps) {
            double stepCount = [totalSteps doubleValueForUnit:[HKUnit countUnit]];
            dispatch_async(dispatch_get_main_queue(), ^{
                // 更新 UI 显示步数
                weakSelf.stepCountLabel.text = [NSString stringWithFormat:@"%f steps", stepCount];
            });
        }
    }];
}];

[self.healthStore executeQuery:query];

2. 读取心率数据

  1. 创建查询:心率数据同样是数量类型,读取心率数据可以使用 HKAnchoredObjectQuery,它可以高效地获取最新的心率数据,并且可以设置锚点来跟踪数据的变化。
// 获取心率数量类型
HKQuantityType *heartRateType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];

// 设置查询锚点(如果有)
HKQueryAnchor *anchor = nil;

// 创建查询
HKAnchoredObjectQuery *heartRateQuery = [[HKAnchoredObjectQuery alloc] initWithType:heartRateType
                                                                       predicate:nil
                                                                          anchor:anchor
                                                                  limit:HKObjectQueryNoLimit
                                                           resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query, NSArray<HKObject *> * _Nullable newSamples, NSArray<HKObject *> * _Nullable deletedObjects, HKQueryAnchor * _Nullable newAnchor, NSError * _Nullable error) {
    if (error) {
        NSLog(@"Heart rate query error: %@", error);
        return;
    }

    // 处理新的心率样本
    for (HKQuantitySample *sample in newSamples) {
        HKQuantity *heartRateQuantity = sample.quantity;
        double heartRate = [heartRateQuantity doubleValueForUnit:[HKUnit unitFromString:@"count/min"]];
        NSLog(@"Heart rate: %f bpm", heartRate);
    }
}];
  1. 执行查询:和步数查询类似,通过 HKHealthStore 执行心率查询。
[self.healthStore executeQuery:heartRateQuery];

写入健康数据

1. 写入步数数据示例

  1. 创建步数样本:假设你的应用记录了用户在某个时间段内的步数,你可以创建一个 HKQuantitySample 来表示这个数据。
// 获取步数数量类型
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

// 创建步数数量
HKQuantity *stepQuantity = [HKQuantity quantityWithUnit:[HKUnit countUnit] doubleValue:1000]; // 假设走了 1000 步

// 设置样本的开始和结束时间
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow: - 60 * 60]; // 一小时前
NSDate *endDate = [NSDate date];

// 创建步数样本
HKQuantitySample *stepSample = [HKQuantitySample quantitySampleWithType:stepType quantity:stepQuantity startDate:startDate endDate:endDate];
  1. 写入样本到 HealthKit:使用 HKHealthStoresaveObject:withCompletion: 方法将步数样本保存到 HealthKit 中。
[self.healthStore saveObject:stepSample withCompletion:^(BOOL success, NSError * _Nullable error) {
    if (!success || error) {
        if (error) {
            NSLog(@"Step sample save error: %@", error);
        }
        // 处理保存失败的情况
        return;
    }
    NSLog(@"Step sample saved successfully.");
}];

处理 HealthKit 数据的单位转换

1. 理解 HealthKit 中的单位

HealthKit 使用 HKUnit 类来表示各种物理量的单位。不同的数据类型可能使用不同的单位,例如步数使用“count”单位,心率使用“count/min”单位,距离可能使用“m”(米)或“km”(千米)单位。

2. 单位转换示例

假设你从 HealthKit 读取到的距离数据是以“m”为单位,但你想在应用中以“km”为单位显示。你可以使用 HKUnit 的方法进行转换。

// 假设从 HealthKit 获取到的距离样本
HKQuantitySample *distanceSample =...;
HKQuantity *distanceQuantity = distanceSample.quantity;

// 从“m”单位转换为“km”单位
HKUnit *kilometerUnit = [HKUnit kilometerUnit];
HKQuantity *convertedDistance = [distanceQuantity quantityByConvertingToUnit:kilometerUnit];
double distanceInKm = [convertedDistance doubleValueForUnit:kilometerUnit];
NSLog(@"Distance in km: %f", distanceInKm);

处理 HealthKit 数据的时间范围和过滤

1. 设置时间范围

在查询健康数据时,经常需要指定一个时间范围,以获取特定时间段内的数据。例如,你可能只想获取今天的步数数据,或者过去一周的心率数据。

  1. 使用 NSPredicate 设置时间范围:可以通过创建一个 NSPredicate 来设置查询的时间范围。对于 HKQuantitySample 类型的数据,可以使用 HKQuery.predicateForSamplesWithStartDate:endDate:options: 方法来创建这个谓词。
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow: - 24 * 60 * 60]; // 过去 24 小时
NSDate *endDate = [NSDate date];

NSPredicate *timePredicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
  1. 将谓词应用到查询中:在创建查询时,将这个时间谓词传递给查询。例如,对于步数统计查询:
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:stepType
                                                                          quantitySamplePredicate:timePredicate
                                                                                  options:HKStatisticsOptionDiscreteAverage
                                                                            anchorDate:startDate
                                                                         intervalComponents:nil];

2. 数据过滤

除了时间范围过滤,你还可以根据其他条件对健康数据进行过滤。例如,你可能只想获取心率在某个范围内的数据。

  1. 创建过滤谓词:假设你只想获取心率在 60 - 100 bpm 之间的数据,可以这样创建谓词:
HKQuantityType *heartRateType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKUnit *heartRateUnit = [HKUnit unitFromString:@"count/min"];

HKQuantity *minHeartRate = [HKQuantity quantityWithUnit:heartRateUnit doubleValue:60];
HKQuantity *maxHeartRate = [HKQuantity quantityWithUnit:heartRateUnit doubleValue:100];

NSPredicate *heartRateFilterPredicate = [HKQuery predicateForSamplesWithStartDate:nil endDate:nil options:HKQueryOptionNone];
heartRateFilterPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[
    heartRateFilterPredicate,
    [HKQuery predicateForQuantity:heartRateType greaterThanOrEqualTo:minHeartRate],
    [HKQuery predicateForQuantity:heartRateType lessThanOrEqualTo:maxHeartRate]
]];
  1. 应用过滤谓词到查询:在创建心率查询时,将这个过滤谓词应用到查询中。
HKAnchoredObjectQuery *heartRateQuery = [[HKAnchoredObjectQuery alloc] initWithType:heartRateType
                                                                       predicate:heartRateFilterPredicate
                                                                          anchor:nil
                                                                  limit:HKObjectQueryNoLimit
                                                           resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query, NSArray<HKObject *> * _Nullable newSamples, NSArray<HKObject *> * _Nullable deletedObjects, HKQueryAnchor * _Nullable newAnchor, NSError * _Nullable error) {
    // 处理查询结果
}];

应对 HealthKit 数据更新和变化

1. 注册数据更新通知

HealthKit 提供了一种机制,让你的应用能够监听健康数据的变化。这对于实时更新 UI 或者在数据变化时执行特定逻辑非常有用。

  1. 注册通知:可以使用 NSNotificationCenter 来注册 HealthKit 数据更新通知。例如,要监听步数数据的变化,可以这样注册:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(stepDataDidChange:) name:HKObjectTypeUpdateNotification object:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]];
  1. 实现通知处理方法:在 stepDataDidChange: 方法中,可以重新查询步数数据并更新 UI。
- (void)stepDataDidChange:(NSNotification *)notification {
    // 重新执行步数查询逻辑
    HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
    //...(创建查询、执行查询等步骤,与前面读取步数数据类似)
}

2. 使用 HKObserverQuery

除了通知,还可以使用 HKObserverQuery 来观察特定类型健康数据的变化。这个查询会在数据发生变化时触发完成块。

  1. 创建 HKObserverQuery:例如,观察心率数据的变化:
HKQuantityType *heartRateType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];

HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:heartRateType predicate:nil updateHandler:^(HKObserverQuery * _Nonnull query, HKObserverQueryCompletionHandler  _Nonnull completionHandler, NSError * _Nullable error) {
    if (error) {
        NSLog(@"Heart rate observer query error: %@", error);
        return;
    }

    // 数据发生变化,重新查询心率数据
    HKAnchoredObjectQuery *newQuery = [[HKAnchoredObjectQuery alloc] initWithType:heartRateType
                                                                       predicate:nil
                                                                          anchor:nil
                                                                  limit:HKObjectQueryNoLimit
                                                           resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query, NSArray<HKObject *> * _Nullable newSamples, NSArray<HKObject *> * _Nullable deletedObjects, HKQueryAnchor * _Nullable newAnchor, NSError * _Nullable error) {
        // 处理新的心率样本
    }];

    [self.healthStore executeQuery:newQuery];

    // 调用完成块,告知 HealthKit 已处理数据变化
    completionHandler();
}];
  1. 执行 HKObserverQuery:通过 HKHealthStore 执行观察查询。
[self.healthStore executeQuery:observerQuery];

HealthKit 与后台模式

1. 后台获取健康数据的场景

在某些情况下,你的应用可能需要在后台获取健康数据。例如,一个健身追踪应用可能需要在后台持续监测用户的步数或心率数据,以便准确记录用户的运动情况。

2. 配置后台模式

  1. 启用后台模式:在 Xcode 项目设置中,导航到“Capabilities”标签,启用“Background Modes”,并勾选“HealthKit”。这将允许你的应用在后台运行时与 HealthKit 进行交互。
  2. 处理后台任务:当应用进入后台时,你需要合理安排 HealthKit 查询任务,确保不会过度消耗系统资源。例如,你可以使用 UIApplicationbeginBackgroundTaskWithExpirationHandler: 方法来启动一个后台任务,并在任务完成后调用 endBackgroundTask: 方法。
__block UIBackgroundTaskIdentifier backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
    [application endBackgroundTask:backgroundTaskIdentifier];
    backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}];

// 在后台执行 HealthKit 查询逻辑
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// 创建并执行步数查询...

// 完成后台任务
[application endBackgroundTask:backgroundTaskIdentifier];
backgroundTaskIdentifier = UIBackgroundTaskInvalid;

通过以上详细的介绍和代码示例,你应该对在 Objective - C 中使用 HealthKit 进行健康数据访问有了全面的了解。从引入框架、请求授权,到读取和写入数据,以及处理数据的各种细节,都为你构建强大的健康类应用提供了坚实的基础。在实际开发中,还需要根据具体的应用需求,进一步优化和扩展这些功能,以提供更好的用户体验。