iOS HealthKit 的理解与正确使用

HealthKit

The HealthKit Framework 

管理 iPhone、iPod touch 或 Apple Watch 上的“健康”数据

https://support.apple.com/zh-cn/HT204351

HealthKit 框架提供了一个结构,应用可以使用它来分享健康和健身数据。

HealthKit 管理从不同来源获得的数据,并根据用户的偏好设置,自动将不同来源的数据合并起来。应用还可以获取每个来源的原始数据,然后执行自己的数据合并。

M7运动协处理器

HealthKit 也可以直接与健康和健身设备一起工作。在iOS8.0中,系统可以自动将兼容的低功耗蓝牙心率仪的数据直接保存在HealthKit存储中。若有M7运动协处理器(iphone5s以上设备才支持),系统可以自动导入计步数据。其他的设备和数据来源必须有配套的应用才可以获取数据并保存在HealthKit中。

HealthKit 提供了一个健康应用来管理用户的健康数据。用户可通过健康应用来查看、添加、删除或者管理其全部的健康和健身数据。用户还可以编辑每种数据类型的分享权限。

HealthKit 和 健康应用在iPad上都不可用。

HealthKit 框架不能用于应用扩展。

HealthKit和隐私

由于健康数据可能是敏感的,HealthKit通过精确控制哪些信息允许应用读取,从而让用户可以控制这些数据。用户必须明确设置每个应用在HealthKit存储中读写的权限。用户可以单独为每种数据类型设置准许或拒绝的权限。例如,用户可以允许你的应用读取计步数据,但是不允许读取血糖水平。为了防止可能的信息泄露,应用是不知道它是否被禁止读取数据的。从应用的角度来看,如果被禁止读取数据,就是没有那种类型的数据存在。

HealthKit的数据不会保存在iCloud中,也不会在多设备间同步。这些数据只会保存在用户的本地设备中。为了安全考虑,当设备没有解锁时,HealthKit存储的数据是加密的。

另外,你的应用如果主要不是提供健康或健身服务的话,那就不能调用HealthKit的API。如果你的应用提供健康和健身服务,就必须要在销售文本和用户界面上明确的表明。特别是下面几条指导适用于所有HealthKit应用。

1. 你的应用不应该将HealthKit收集的数据用于广告或类似的服务。注意,可能在使用HealthKit框架应用中还是要服务广告,但是你不能使用HealthKit中的数据来服务广告。

2. 在没有用户的明确允许下,你不能向第三方展示任何HealthKit收集的数据。即使用户允许,你也只能向提供健康或健身服务的第三方展示这些数据。

3. 你不能将HealthKit收集的数据出售给广告平台、数据代理人或者信息经销商。

4. 如果用户允许,你可以将HealthKit数据共享给第三方用于医学研究。

5. 你必须明确说明,你和你的应用会怎样使用用户的HealthKit数据。

你必须为每个使用HealthKit框架的应用提供一份隐私策略。你可以在以下网站找到创建隐私策略的指导:

1. Personal Health Record model (for non-HIPAA apps): http://www.healthit.gov/policy-researchers-implementers/personal-health-record-phr-model-privacy-notice 

2. HIPAA model (for HIPAA covered apps): http://www.hhs.gov/ocr/privacy/hipaa/modelnotices.html 

这些模板由ONC开发,通过直白的语言和平易近人的设计来解释用户数据是如何被收集和共享的,从而提高用户的体验和理解。这些不是用来替代基本的互联网隐私策略,而且开发者应该咨询ONC来了解你的应用适用哪种模板。这些模板仅供参考,Apple不承担你使用这些模板带来的后果。

参看App Store Review Guidelines中的HealthKit章节来了解更多,可以在Apple的 App Review Support  页面中找到。关于使用敏感用户数据的更多技术信息,参见 App Programming Guide for iOS  中的 Best Practices for Maintaining User Privacy  。

HealthKit的设计思路

HealthKit是用来在应用间以一种有意义的方式共享数据。为了达到这点,框架限制只能使用预先定义好的数据类型和单位。这些限制保证了其他应用能理解这些数据是什么意思已经怎样使用。因此,开发者不能创建自定义数据类型和单位。而HealthKit尽量会提供一个应该完整的数据类型和单位。

框架还大量使用了子类化,在相似的类间创建层级关系。通常这些类间都有一些细微但是重要的差别。还有不少很相关的类,需要正确地区别开才能一起工作。例如HKObject  和 HKObjectType 抽象类有很多平行层级的子类。当使用object和object type时,必须确保使用匹配的子类。

HealthKit中所有的对象都是HKObject的子类。大部分 HKObject 对象子类都是不可变的。每个对象都有下列属性:

1.  UUID。每个对象的唯一标示符。

2.  Source。数据的来源。来源可以是直接把数据存进HealthKit的设备,或者是应用。当一个对象保存进HealthKit中时,HealthKit会自动设置其来源。只有从HealthKit中获取的数据source属性才可用。

3.  Metadata。一个包含关于该对象额外信息的字典。元数据包含预定义和自定义的键。预定义的键用来帮助在应用间共享数据。自定义的键用来扩展HealthKit对象类型,为对象添加针对应用的数据。

HealthKit对象主要分为2类:特征和样本。特征对象代表一些基本不变的数据。包括用户的生日、血型和生理性别。你的应用不能保存特征数据。用户必须通过健康应用来输入或者修改这些数据。

样本对象代表某个特定时间的数据。所有的样本对象都是HKSample 的子类。它们都有下列属性:

1.  Type。样本类型。例如,这可能包括一个睡眠分析样本、一个身高样本或者一个计步样本。

2.  Start date。样本的开始时间。

3.  End date。样本的结束时间。如果样本代表时间中的某一刻,结束时间和开始时间相同。如果样本代表一段时间内收集的数据,结束时间应该晚于开始时间。

样本可以细分为四个样本类型。

1.  类别样本。这种样本代表一些可以被分为有限种类的数据。在iOS8.0中,只有一种类别样本,睡眠分析。更多信息,参见HKCategorySample Class Reference 。

2.  数量样本。这种样本代表一些可以存储为数值的数据。数量样本是HealthKit中最常见的数据类型。这些包括用户的身高和体重,还有一些其他数据,例如行走的步数,用户的体温和脉搏率。更多信息,参见HKQuantitySample Class Reference 。

3.  Correlation。这种样本代表复合数据,包含一个或多个样本。在iOS8.0中,HealthKit使用correlation来代表食物和血压。在创建书屋或者血压数据时,你应该使用correlation。更多信息,参见 HKCorrelation Class Reference 。

4.  Workout。Workout代表某些物理活动,像跑步、游泳,甚至游戏。Workout通常有类型、时长、距离、和消耗能量这些属性。你还可以为一个workout关联许多详细的样本。不像correlation,这些样本是不包含在workout里的。但是,它们可以通过workout获取到。更多信息,参见 HKWorkout Class Reference 。

设置HealthKit

在你开始使用HealthKit之前,必须要执行下列步骤:

1. 在Xcode中打开HealthKit功能。

2. 调用 isHealthDataAvailable  方法来查看HealthKit在该设备上是否可用。HealthKit在iPad上不可用。

3. 为你的应用实例化一个 HKHealthStore  对象。每个应用只需要一个HealthKit存储实例。这个存储实例就是你和HealthKit数据库交互的主要接口。

4. 使用 requestAuthorizationToShareTypes:readTypes:completion: 方法来请求获取HealthKit数据的权限。对每种类型的数据,你都必须请求许可来共享和读取。

如果用户允许分享某种类型的数据,那么你可以创建这种类型的新样本,并保存在HealthKit中。你应该使用 authorizationStatusForType: 来检查是否允许分享这种类型的数据。

如果用户允许读取某种类型的数据,那么你就可以从HealthKit中读取这些数据。不幸的是,即使知道用户拒绝读取某种类型的数据,也可能会显示出潜在的健康问题。因此,你的应用无法确定用户是否允许读取数据。如果你没有得到读取某种数据的许可,那简单来看就好像是HealthKit中没有这种类型的数据。

关于设置HealthKit的更多内容,参见HKHealthStore Class Reference 。

有一个展示了如何设置和使用HealthKit的例子,参见Fit: Store and Retrieve HealthKit Data 。

向HealthKit 存储中添加样本

你的应用可以创建新的样本,并添加到HealthKit存储中。对所有样本类型来说,大致的过程都是类似的,但是又有一些不同。

1.  在 HealthKit Constants Reference  中找到正确的类型标识符。

2.  使用类型标识符创建一个匹配的 HKObjectType  子类。有一些便捷的方法,参见  HKObjectType Class Reference 。

3.  使用对象类型,创建一个匹配的 HKSample  子类。

4.  使用 saveObject:withCompletion:  方法将对象保存到HealthKit 存储中。

每个 HKSample  子类都有自己的便捷方法来实例化样本对象。流程如下图所示。


对于数量样本,你必须创建一个 HKQuantity   类的实例。数量的单位必须和类型标识符文档中描述的可用单位相关。例如,HKQuantityTypeIdentifierHeight  文档中说明它使用长度单位,因此,你的数量必须使用厘米、米、英尺、英寸或者其他长度单位。更多信息,参见 HKQuantitySample Class Reference 。

对于类别样本,它的值必须和类型标识符文档中描述的枚举值相关。例如, HKCategoryTypeIdentifierSleepAnalysis  文档中说明它使用 HKCategoryValueSleepAnalysis  枚举值。因此你在创建样本时必须从这个枚举中传递一个值。更多信息,参见 HKCategorySample Class Reference 。

对于correlation,你必须先创建correlation包含的所有样本。correlation的类型标识符描述了它可以包含的类型和对象的数量。不要把被包含的对象存进HealthKit。它们是以correlation的一部分存储的。更多信息,参见HKCorrelation Class Reference 。

workout和其他类型的样本有些不同。首先,创建 HKWorkoutType  实例并不需要指定类型标识符。所有的workout都是用同样的类型标识符。第二,对于每个workout你都需要提供一个 HKWorkoutActivityType  值。这个值定义了workout中执行的活动的类型。最后,当workout保存到HealthKit后,你可以给workout关联额外的样本。这些样本提供了workout的详细信息。更多信息,参见HKWorkout Class Reference 。

读取HealthKit数据

HealthKit提供了许多方法来读取数据。

1.  直接方法调用。HealthKit提供了直接读取特征数据的方法。这些方法只能用于读取特征数据。更多信息,参见 HKHealthStore Class Reference。

2.  样本查询。这是使用最多的查询。使用样本查询来读取任何类型的样本数据。当你想要对结果进行排序或者限制返回的样本总数时,样本查询就特别有用。更多信息,参见 HKSampleQuery Class Reference 。

3.  观察者查询。这是一个长时间运行的查询,它会检测HealthKit存储,并在匹配到的样本发生变化时通知你。如果当存储发生变化时你想得到通知,就使用观察者查询。你可以让这些查询在后台执行。更多信息,参见HKObserverQuery Class Reference 。

4.  锚定对象查询。用这种查询来搜索添加进存储的项。当锚定查询第一次执行时,会返回存储中所有匹配的样本。在接下来的执行中,只会返回上一次执行之后添加的项目。通常,锚定对象查询会和观察者查询一起使用。观察者查询告诉你某些项目发生了变化,而锚定对象查询来决定有哪些(如果有的话)项目被添加进了存储。更多信息,参见 HKAnchoredObjectQuery Class Reference 。

5.  统计查询。使用这种查询来在一系列匹配的样本中执行统计运算。你可以使用统计查询来计算样本的总和、最小值、最大值或平均值。更多信息,参见 HKStatisticsQuery Class Reference 。

6.  统计集合查询。使用这种查询来在一系列长度固定的时间间隔中执行多次统计查询。通常使用这种查询来生成图表。查询提供了一些简单的方法来计算某些值,例如,每天消耗的总热量或者每5分钟行走的步数。统计集合查询是长时间运行的。查询可以返回当前的统计集合,也可以监测HealthKit存储,并对更新做出响应。更多信息,参见 HKStatisticsCollectionQuery Class Reference 。

7.  Correlation查询。使用这种查询来在correlation查找数据。这种查询可以为correlation中每个样本类型包含独立的谓词。如果你只是想匹配correlation类型,那么请使用样本查询。更多信息,参见 HKCorrelation Class Reference 。

8.  来源查询。使用这种查询来查找HealthKit存储中的匹配数据的来源(应用和设备)。来源查询会列出储存的特定样本类型的所有来源。更多信息,参见HKSourceQuery Class Reference 。

单位换算

HealthKit使用 HKUnit  和 HKQuantity  类来支持单位。HKUnit  提供了单一单位的表示。它支持大部分的公制和英制单位,当然还包括基本单位和符合单位。基本单位代表单一的度量,例如米、磅或者秒。复合单位使用数学运算连接一个或多个基本单位,例如m/s或者lb/ft2。

HKUnit  提供了便捷方法来创建HealthKit支持的所有基本单位。它还提供了构建复合单位需要的数学运算。最后,你还可以通过直接使用恰当的格式化的单位字符串来创建复合单位。

更多信息,参见HKUnit Class Reference 。

HKQuantity  类存储了给定单位的值。之后你可以用任何兼容的单位来取值。这样,你的应用就可以很轻松的完成单位换算。

更多信息,参见 HKQuantity Class Reference 。

在某些场合,你可以使用格式化器来本地化数量。iOS8提供了提供了新的格式化器来处理长度(NSLengthFormatter)、质量(NSMassFormatter)和能量(NSEnergyFormatter)。对于其他的数量,你需要自己来换算单位和本地化数据。

多线程

HealthKit存储是线程安全的,大部分HealthKit对象是不可变的。通常来说,你可以在多线程环境中安全地使用HealthKit。

注意:

所有HealthKit API的完成回调都在一个私有的后台队列中执行。所以在你更新用户界面或者修改一些只能在主线程中处理的资源之前,你应该把这些数据传回主线程。

关于多线程和并发编程的更多信息,参见 Concurrency Programming Guide 。

添加数字签名

HealthKit中的数字签名元数据对象提供了由可信设备生成的样本记录的数据完整性。元数据项存储了一份数字签名过的样本记录副本。签名是由设备生成的(由于设备存储了私有签名密钥,所以设备应该是防篡改的)。这样数据的使用者就可以通过设备的公钥来检查签名,进而验证记录数据是否被修改过。由于每条记录都是独立签名的,所以每条记录总共能存储大约1K bytes的签名。因此,这种元数据签名项是给那些样本率最多在每天几次的记录使用的。更高的样本率需要对样本组进行签名,这已经超出了本文档的范围。

通常,私钥是在制造时置入防篡改的测量设备中的。(私钥重置或证书更新的政策或机制不在本文档的范围内)相关的公钥会由设备制造商公布,例如在它们的网站上。设备应该将每个样本的样本数据和签名传给iOS应用,这两者都会储存在HealthKit数据库中。注意,数字签名的公私钥是用来提供数据完整性检查的,不是用来加密的。数据记录中的实际值都是明文。

数字签名的格式是IETF RFC 5652中的CMS(Cryptographic Message Syntax)。签名使用ASN.1的DER(Distinguished Encoding Rules)编码。使用的信息摘要应该是SHA256,签名加密应该是FIPS PUB 186-4 Digital Signature Standard Elliptic Curve P-256。这样长度和效率都有保证。另外,整个签名都应该使用bsae64编码,这样就能存储在HealthKit NSString 元数据对象中。

签名应该是ASN.1 Signed-data Content Type:

SignedData ::= SEQUENCE {  version CMSVersion,  digestAlgorithms DigestAlgorithmIdentifiers,  encapContentInfo EncasulatedContentInfo,  signerInfos SignerInfo }

SignerInfo Type是:

SignerInfo ::= SEQUENCE {  version CMSVersion,  sid SignerIdentifier,  digestAlgorithm DigestAlgorithmIdentifier,  signatureAlgorithem SignatureAlgorithmIdentifier,  signatureSignatureValue }

上面就是摘要和签名的算法。可选项目都没有包含在其中。SignerIdentifier用来取得适当的公钥,来对签名进行验证。

EncapsulatedContentInfo是从由设备生成的样本记录中的相关项目的副本。副本应该用ASN.1 DER编码,并应至少包含一个样本时间戳和采样值。将记录数据拷贝进签名内,是为了有一个稳定的定义良好的二进制编码(ASN.1 DER),这对于生成一个可验证的签名来说是必要的。ASN.1编码的plist结构(键值对),应该能满足大部分记录类型。(参见“使用ASN.1 DER来编码plist结构”)

#import "HealthManager.h"

#import "HealthModel.h"

@interface HealthManager ()

@property (nonatomic, strong) HKHealthStore *healthStore;

@end

static HealthManager *_share_HealthManager = nil;

@implementation HealthManager

+ (instancetype) shareHealthManager

{

if (_share_HealthManager == nil) {

_share_HealthManager = [[HealthManager alloc] init];

}

return _share_HealthManager;

}

#pragma mark - getter

- (HKHealthStore *)healthStore {

if (!_healthStore) {

_healthStore = [[HKHealthStore alloc] init];

}

return _healthStore;

}

+ (BOOL)isHealthDataAvailable {

return [HKHealthStore isHealthDataAvailable];

}

- (void)authorizateHealthKit:(void (^)(BOOL isAuthorizateSuccess))resultBlock {

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

//读操作

NSSet *readObjectTypes = [NSSet setWithObjects:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned] ,nil];

//写操作

/*心率,血糖,舒张压 ,收缩压*/

NSSet *writeObjectTypes = [NSSet setWithObjects:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodGlucose],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic],[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic], nil];

[self.healthStore requestAuthorizationToShareTypes:writeObjectTypes readTypes:readObjectTypes completion:^(BOOL success, NSError * _Nullable error) {

if (resultBlock) {

resultBlock(success);

}

}];

});

}

/*!

*

*

*  @brief  获取卡路里

*/

- (void)getKilocalorieUnitCompletionHandler:(void(^)(double value, NSError *error))handler

{

//    查询的基类是HKQuery,这是一个抽象类,能够实现每一种查询目标

//    查询采样信息

HKSampleType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];

NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:nil endDate:nil options:HKQueryOptionStrictStartDate];

//NSSortDescriptors用来告诉healthStore

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:NO];

HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {

if(!error && results) {

HKQuantitySample *samples = results.firstObject;

HKQuantity *energyBurned = samples.quantity;

double value = [energyBurned doubleValueForUnit:[HKUnit kilocalorieUnit]];

if(handler)

{

handler(value,error);

}

}

else

{

if(handler)

{

double value = 0.0;

handler(value,error);

}

}

}];

//执行查询

[self.healthStore executeQuery:sampleQuery];

//    HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];

//

//        HKStatisticsQuery *query = [[HKStatisticsQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options:HKStatisticsOptionCumulativeSum completionHandler:^(HKStatisticsQuery *query, HKStatistics *result, NSError *error) {

//            HKQuantity *sum = [result sumQuantity];

//

//

//

//

//            double value = [sum doubleValueForUnit:[HKUnit kilocalorieUnit]];

//

//            if(handler)

//            {

//                handler(value,error);

//            }

//        }];

//

//    [self.healthStore executeQuery:query];

}

- (void)readStepCount:(void (^)(NSString *stepCount))LatestStepCountResultBlock

{

/**********************************

查询全部时间段的步数

**********************************/

//查询的基类是HKQuery,这是一个抽象类,能够实现每一种查询目标

//查询采样信息

//    HKSampleType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

//    NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:nil endDate:nil options:HKQueryOptionStrictStartDate];

//    //NSSortDescriptors用来告诉healthStore

//    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:NO];

//    HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {

//        if(!error && results) {

//            for(HKQuantitySample *samples in results) {

//                NSLog(@"%@ 至 %@ : %@", samples.startDate, samples.endDate, samples.quantity);

//            }

//        }

//        else

//        {

//

//        }

//

//    }];

//    //执行查询

//    [self.healthStore executeQuery:sampleQuery];

/**********************************

查询按天查询的步数

**********************************/

[self fetchAllHealthDataByDay:^(NSArray *modelArray)

{

dispatch_async(dispatch_get_main_queue(), ^{

if (modelArray.count > 0) {

HealthModel *healthModel = (HealthModel *)modelArray.firstObject;

//NSString *result = [NSString stringWithFormat:@"%@,%ld",[NSString stringWithFormat:@"%zd年%zd月%zd日",healthModel.startDateComponents.year,healthModel.startDateComponents.month,healthModel.startDateComponents.day],(long)healthModel.stepCount];

NSString *result = [NSString stringWithFormat:@"%ld",(long)healthModel.stepCount];

LatestStepCountResultBlock(result);

}

else

{

NSString *result = @" 0";

LatestStepCountResultBlock(result);

}

});

//        for ( HealthModel *healthModel in modelArray) {

//

//            self.stepLabel.text = F(@"%@,%ld",F(@"%zd年%zd月%zd日", healthModel.startDateComponents.year, healthModel.startDateComponents.month, healthModel.startDateComponents.day),healthModel.stepCount);

//        }

}];

}

- (void)fetchAllHealthDataByDay:(void (^)(NSArray *modelArray))queryResultBlock {

HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];

intervalComponents.day = 1;

__block NSCalendar *calendar;

#ifdef IOS8

calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];

#else

calendar = [NSCalendar calendarWithIdentifier:NSGregorianCalendar];

#endif

NSDateComponents *currentComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour fromDate:[NSDate date]];

NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow: - (currentComponents.hour * 3600 + currentComponents.minute * 60 + currentComponents.second)];

NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear

fromDate:endDate];

[self executeQueryForQuantityType:quantityType

predicate:nil

anchorDate:[calendar dateFromComponents:anchorComponents]

intervalComponents:intervalComponents

callBackResult:^(HKStatisticsCollection * _Nullable result, NSError *error) {

if (error) {

NSLog(@"an error occurred while calculating the statistics %@",error.localizedDescription);

} else {

__block NSMutableArray *tempArray = @[].mutableCopy;

[result.statistics enumerateObjectsUsingBlock:^(HKStatistics * _Nonnull statistics, NSUInteger idx, BOOL * _Nonnull statisticsStop) {

[statistics.sources enumerateObjectsUsingBlock:^(HKSource * _Nonnull source, NSUInteger idx, BOOL * _Nonnull sourceStop) {

if ([source.name isEqualToString:[UIDevice currentDevice].name]) {//只取设备的步数,过滤其他第三方应用的

double stepCount = [[statistics sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];

@autoreleasepool {

//数据封装

HealthModel *healthModel = [[HealthModel alloc] init];

healthModel.startDateComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear

fromDate:statistics.startDate];

healthModel.endDateComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear

fromDate:statistics.endDate];

healthModel.stepCount = stepCount;

[tempArray insertObject:healthModel atIndex:0];//倒序

}

*sourceStop = YES;

}

}];

}];

if (queryResultBlock) {

queryResultBlock(tempArray);

}

}

}];

}

//查询

- (void)executeQueryForQuantityType:(HKQuantityType *)quantityType

predicate:(nullable NSPredicate *)quantitySamplePredicate

anchorDate:(NSDate *)anchorDate

intervalComponents:(NSDateComponents *)intervalComponents

callBackResult:(void (^)(HKStatisticsCollection * __nullable result, NSError *error))queryResult {

HKStatisticsCollectionQuery *collectionQuery =

[[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType

quantitySamplePredicate:quantitySamplePredicate

options:HKStatisticsOptionCumulativeSum | HKStatisticsOptionSeparateBySource

anchorDate:anchorDate

intervalComponents:intervalComponents];

collectionQuery.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection * __nullable result, NSError * __nullable error){

if (queryResult) {

queryResult(result, error);

}

};

[self.healthStore executeQuery:collectionQuery];

}

-(void)saveBloodDataToHealthstoreWithData:(BloodDataModel *)dataModel

{

HKQuantitySample *HeartRateSample,*BloodGlucoseSample,*BloodPressureDiastolicSample,*BloodPressureSystolicSample;

HKCorrelation  *BloodPressureCorrelation;

NSMutableArray *saveTypesArray = [[NSMutableArray alloc] init];

if (dataModel.HeartRate) {

HKUnit *HeartRate = [HKUnit unitFromString:@"count/min"];

HeartRateSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate] quantity:[HKQuantity quantityWithUnit:HeartRate doubleValue:[NSNumber numberWithInteger:dataModel.HeartRate].doubleValue] startDate:dataModel.date endDate:dataModel.date];

[saveTypesArray addObject:HeartRateSample];

}

if (dataModel.BloodGlucose) {

BloodGlucoseSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodGlucose] quantity:[HKQuantity quantityWithUnit:[HKUnit unitFromString:@"mg/dL"]doubleValue:dataModel.BloodGlucose*18.0] startDate:dataModel.date endDate:dataModel.date];

[saveTypesArray addObject:BloodGlucoseSample];

}

if (dataModel.BloodPressureDiastolic && dataModel.BloodPressureSystolic) {

BloodPressureDiastolicSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic] quantity:[HKQuantity quantityWithUnit:[HKUnit millimeterOfMercuryUnit] doubleValue:dataModel.BloodPressureDiastolic] startDate:dataModel.date endDate:dataModel.date];

BloodPressureSystolicSample = [HKQuantitySample quantitySampleWithType:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic] quantity:[HKQuantity quantityWithUnit:[HKUnit millimeterOfMercuryUnit] doubleValue:dataModel.BloodPressureSystolic] startDate:dataModel.date endDate:dataModel.date];

BloodPressureCorrelation = [HKCorrelation correlationWithType:[HKCorrelationType correlationTypeForIdentifier:HKCorrelationTypeIdentifierBloodPressure] startDate:dataModel.date endDate:dataModel.date objects:[NSSet setWithObjects:BloodPressureDiastolicSample,BloodPressureSystolicSample, nil]];

[saveTypesArray addObject:BloodPressureCorrelation];

}

[self.healthStore saveObjects:saveTypesArray withCompletion:^(BOOL success,NSError * _Nullable error)

{

if (error!= nil) {

NSLog(@"Error saving sample:%@",error.localizedDescription);

}

else

{

NSLog(@"Sample saved successfully!");

}

}];

}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容