简介
在HeathKit中,能够运用HKHealthStore类来拜访健康App中的数据,如健身记载、养分摄入、睡觉状况等。也能够对这些数据进行读取和同享(即第三方App写入或删去数据到苹果健康App中)。笔者就以获取步数为例,还能够获取身高、体重、睡觉时刻、心率等等。
运用前
在Xcode中, 翻开HealthKit 功用
在info.plist文件中,增加NSHealthShareUsageDescription用于读取数据的描绘和NSHealthUpdateUsageDescription用于写入数据的描绘。
HeathKit
不支撑在iPad中运用,并且它也不支撑扩展。
HeathKit框架
主要是抽象类HKObject
+ HKObjectType
。
HKObject
在抽象类HKObject
的子类中,每个目标都有下面的特点:
- UUID:目标的标识符
- source:数据的来历,来历可所以健康App,也能够第三方App。目标存储到
HeathKit
中时会设置其来历。只要从HeathKit
中获取到的数据的来历才有用。 - metadata:一个包括该目标额定信息的字典,元数据包括预界说的key和自界说的key,预界说的key用来帮助咱们在运用间同享数据,而自界说的key用来扩展HeathKit,为目标增加针对运用的数据。
特征和样本
HeathKit
的目标主要分为特征和样本(开端套娃):
- 特征:用户的基本不变的数据,包括用户的生日、血型和性别等。只能用户在健康App中增加或修正。
- 样本:某个时刻段的数据,其目标都是
HKSample
的子类,有以下特点:- type:样本类型,例如:步数、间隔、心率等,其类型又能够分为以下四种:
-
HKCategorySample
:类别样本,iOS 8 中,只要睡觉分析这一个类别样本。代表有限品种的样本。 -
HKQuantitySample
:代表存储数据的样本,比方步数、间隔、用户的体温等。是最常见的数据类型。 -
HKCorrelation
:代表复合数据,包括一个或许多个样本。在iOS 8 中,用correlation代表食物和血压。在创立食物或血压时,需求用correlation。 -
HKWorkout
:代表某种活动,比方走、跑步等。包括有开端时刻、完毕时刻、运动类型、耗费能量、运动间隔等特点。还能够为workout相关许多具体的样本。不像correlation,这些样本不包括在workou中,可是能够通workout获取到。
-
- startDate:样本采样开端时刻。
- endDate:样本采样完毕时刻。(存在startDate == endDate)
- type:样本类型,例如:步数、间隔、心率等,其类型又能够分为以下四种:
常用的数据类型:
-
HKQuantityTypeIdentifierBodyMassIndex
: 体重指数 -
HKQuantityTypeIdentifierBodyFatPercentage
: 体脂百分比 -
HKQuantityTypeIdentifierHeight
: 身高 -
HKQuantityTypeIdentifierBodyMass
: 体重 -
HKQuantityTypeIdentifierLeanBodyMass
: 瘦体重 -
HKQuantityTypeIdentifierWaistCircumference
: 腰围 -
HKQuantityTypeIdentifierStepCount
: 步数 -
HKQuantityTypeIdentifierDistanceWalkingRunning
: 步行+跑步间隔 -
HKQuantityTypeIdentifierFlightsClimbed
: 上楼梯数 -
HKQuantityTypeIdentifierDistanceSwimming
: 游泳间隔 -
HKQuantityTypeIdentifierDistanceDownhillSnowSports
: 滑雪间隔 -
HKQuantityTypeIdentifierHeartRate
: 心率 -
HKQuantityTypeIdentifierBodyTemperature
: 身体温度 -
HKQuantityTypeIdentifierBloodPressureSystolic
: 血液收缩压 -
HKQuantityTypeIdentifierBloodPressureDiastolic
: 血液舒张压 -
HKQuantityTypeIdentifierRespiratoryRate
: 用户呼吸率 -
HKQuantityTypeIdentifierRestingHeartRate
: 静息心率 -
HKQuantityTypeIdentifierWalkingHeartRateAverage
: 步行时心率
一共有超过100项数据类型。现在常用或是能用也就这几项。
需求查询其他类型的数据只需替换类型即可。
HKUnit
读取的数据的单位的类,比方体重的单位kg,时刻单位min(minutes)、hr(hours)、d(days)等。一般在获取数据或写入样本数据的时分需求增加对应的单位,如多少步、多少米等。
HKQuery
读取数据的方法,大致以下几类:
- HKHealthStore:供给了一个用于拜访和存储用户健康数据的接口。
- HKSampleQuery:样本查询。这是运用最多的查询。运用样本查询能够查询在HeathKit中恣意的数据。并且能够对成果进行排序等。
- HKObserverQuery:观察者查询的类:这是一个长时刻运转的查询,它会检测HealthKit存储,并在匹配到的样本发生改变时告知你(能够后台)。
- HKAnchoredObjectQuery:锚定目标查询。用这种查询来搜索增加进存储的项。当锚定查询第一次履行时,会回来存储中一切匹配的样本。在接下来的履行中,只会回来上一次履行之后增加的项目。通常,锚定目标查询会和观察者查询一起运用。观察者查询告知你某些项目发生了改变,而锚定目标查询来决定有哪些(假如有的话)项目被增加进了存储。
- HKStatisticsQuery:核算查询。运用这种查询来在一系列匹配的样本中履行核算运算。即核算与给定数量类型和谓词匹配的数量样本的核算信息。你能够运用核算查询来核算样本的总和、最小值、最大值或平均值。
- HKStatisticsCollectionQuery:核算调集查询。运用这种查询来在一系列长度固定的时刻间隔中履行多次核算查询。通常运用这种查询来生成图表。查询供给了一些简略的方法来核算某些值,例如,每天耗费的总热量或许每5分钟行走的步数。核算调集查询是长时刻运转的。查询能够回来当前的核算调集,也能够监测HealthKit存储,并对更新做出响应。
- HKCorrelation:Correlation查询。运用这种查询来在correlation查找数据。这种查询能够为correlation中每个样本类型包括独立的谓词。假如你只是想匹配correlation类型,那么请运用样本查询。
- HKSourceQuery:来历查询。运用这种查询来查找HealthKit存储中的匹配数据的来历(运用和设备)。来历查询会列出贮存的特定样本类型的一切来历。
注意事项
由于是健康App的步数是可写入的,所以想要获取实在的步数,就需求将手动写入的数据过滤,即:实在步数 = 总步数 – 写入的步数。
获取步数完好代码
#import "ViewController.h"
#import <HealthKit/HealthKit.h>
@interface ViewController ()
// 创立healthStore实例目标
@property (nonatomic,strong) HKHealthStore *healthStore;
// 查询数据的类型,比方计步,行走+跑步间隔等等
@property (nonatomic,strong) HKQuantityType *quantityType
// 谓词,用于限制查询回来成果
@property (nonatomic,strong) NSPredicate *predicate;
@end
@implementation ViewController
- (HKHealthStore *)healthStore{
if(_healthStore == nil){
_healthStore = [[HKHealthStore alloc]init];
}
return _healthStore;
}
- (HKQuantityType *)quantityType{
if(_quantityType == nil){
_quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
}
return _quantityType;
}
- (NSPredicate *)predicate{
if(_predicate == nil){
// 构造当天时刻段查询参数
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
// 开端时刻
NSDate *startDate = [calendar startOfDayForDate:now];
// 完毕时刻
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];
_predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
}
return _predicate;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self useHealthKit];
}
// 注意代码块中的循环引用,这儿疏忽
- (void)useHealthKit{
//判别设备是否支撑检查healthKit数据
if([HKHealthStore isHealthDataAvailable] == NO){
NSLog(@"设备不支撑healthKit");
return;
}
// 这儿只获取运动步数的权限
NSSet *readObjectTypes = [NSSet setWithObjects:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount], nil];
// 向用户恳求授权同享或读取健康App数据
[self.healthStore requestAuthorizationToShareTypes:nil readTypes:readObjectTypes completion:^(BOOL success, NSError * _Nullable error) {
if(success){
[self queryTotalStepCount:^(NSInteger stepCount) {
NSLog(@"实在运动步数(总步数 - 修改的步数) = %ld",(long)stepCount);
}];
}else{
NSLog(@"获取步数权限失败");
}
}];
}
//这儿的步数是总步数,和各个来历 包括手动修改录入的
// HKStatisticsOptionCumulativeSum 总步数
// HKStatisticsOptionSeparateBySource 健康App一切步数数据的来历,包括iPhone、iWatch、健康App、第三方App等
- (void)queryTotalStepCount:(void(^)(NSInteger stepCount))completion{
HKStatisticsQuery *query = [[HKStatisticsQuery alloc]initWithQuantityType:self.quantityType quantitySamplePredicate:self.predicate options:HKStatisticsOptionCumulativeSum|HKStatisticsOptionSeparateBySource completionHandler:^(HKStatisticsQuery * _Nonnull query, HKStatistics * _Nullable result, NSError * _Nullable error) {
if (error) {
NSLog(@"获取失败!");
!completion?:completion(0);
return;
}
// 总步数
double totalStepCount = [result.sumQuantity doubleValueForUnit:[HKUnit countUnit]];
// 健康App修改的步数
double userEnteredCount = 0;
// 遍历数据来历,取得健康App修改的数值
for(HKSource *source in result.sources){
if([source.name isEqualToString:@"健康"]){
userEnteredCount = [[result sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];
}
}
!completion?:completion((NSInteger)(totalStepCount - userEnteredCount));
}];
[self.healthStore executeQuery:query];
}
@end
正常状况下,过滤用户在健康App手动输入的步数后就能较精确的数值,可是第三方App在获取授权后也能够向健康App写入样本数据,如增加在某个开端时刻到完毕时刻段内,行走了多少步的数据。
死磕?考虑?
接下来咱们简略深化下,咱们顺次做下以下操作:
- 咱们用上面代码读取步数,比较健康App、微信、QQ、Keep、支付宝的数值
- 在健康App手动修改输入10000步,再重复过程一
- 运用代码写入10000步,再重复过程一
在进行上述测试时,笔者的手机和手表放在桌面,保证不会有步数数据更新。
过程一
咱们运用HKSampleQuery
查询下,取得每个样本更具体的数据:
// 成果排序,从开端到完毕顺次
NSSortDescriptor *startSortDec = [NSSortDescriptor sortDescriptorWithKey:HKPredicateKeyPathStartDate ascending:NO];
NSSortDescriptor *endSortDec = [NSSortDescriptor sortDescriptorWithKey:HKPredicateKeyPathEndDate ascending:NO];
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc]initWithSampleType:self.quantityType predicate:self.predicate limit:HKObjectQueryNoLimit sortDescriptors:@[startSortDec,endSortDec] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) {
if(error){
!completion?:completion(0);
return;
}else{
// 单位
HKUnit *unit = [HKUnit countUnit];
// 核算iPhone记载的步数
NSInteger iPhoneCount = 0;
// 核算iWatch记载的步数
NSInteger iWatchCount = 0;
// 核算健康App手动修改的步数
NSInteger userEnteredCount = 0;
// 核算第三方App写入的步数
NSInteger thirdAppCount = 0;
// 遍历样本
for (HKQuantitySample *sample in results){
// 样本步数
NSInteger count = (NSInteger)[sample.quantity doubleValueForUnit:unit];
// 设备名称
NSString *deviceName = sample.device.name;
if (deviceName == nil) { // 包括手动修改和第三方App写入
// 判别用户手动录入的数据。
NSInteger isUserEntered = [sample.metadata[HKMetadataKeyWasUserEntered] integerValue];;
if(isUserEntered == 1){
userEnteredCount += count;
}else{
thirdAppCount += count;
}
}else if ([deviceName isEqualToString:@"iPhone"]){
iPhoneCount += count;
}else if ([deviceName isEqualToString:@"Apple Watch"]){
iWatchCount += count;
}
}
NSLog(@"iPhone记载的步数 = %ld",(long)iPhoneCount);
NSLog(@"iWatch记载的步数 = %ld",(long)iWatchCount);
NSLog(@"健康App手动修改的步数 = %ld",(long)userEnteredCount);
NSLog(@"第三方App写入的步数 = %ld",(long)thirdAppCount);
// 主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
!completion?:completion(userEnteredCount);
});
}
}];
[self.healthStore executeQuery:sampleQuery];
运转:
健康App截图:
微信截图:
QQ截图:
QQ特意分两种状况,授权运用健康App前后两种状况。 未授权截图:
授权后截图:
以上可知QQ获取运动步数的方法是,若用户授权运用健康App,则读取健康App的数据;若用户未授权则运用CMPedometer
传感器获取。
Keep截图:
支付宝截图:
总结
多数App优先运用的仍是健康App的数据,其次运用CMPedometer
传感器获取运动步数。其间Keep作为一款主打运动的运用,可能有自己的优化或算法也算正常。
过程二
在健康App手动修改输入10000步:
代码读取:
健康App截图:
微信运动截图:
QQ运动截图:
Keep截图:
支付宝运动截图:
总结
在健康App手动输入10000步后,咱们运用代码能够识别出手动修改的,并核算实在的步数。除了Keep之外,其他的App操作均相同,过滤了手动修改值的影响。
记住咱们现在比较精确的步数是3288步。
过程三
运用代码写入10000步,再重复过程一
写入步数代码(也能够删去数据,这儿就不演示了):
// 向健康App写入步数
- (void)userEnteredStepCount:(double)step{
// 授权写入的数据类型为步数
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSSet *shareTypes = [NSSet setWithObjects:stepType, nil];
[self.healthStore requestAuthorizationToShareTypes:shareTypes readTypes:nil completion:^(BOOL success, NSError * _Nullable error) {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
// 开端时刻,这儿为当天的零点时刻
NSDate *startDate = [calendar startOfDayForDate:now];
// 多少步
HKQuantity *stepQuantity = [HKQuantity quantityWithUnit:[HKUnit countUnit] doubleValue:step];
// 创立样本(某个时刻段或时刻内,行走多少步)
HKQuantitySample *stepSample = [HKQuantitySample quantitySampleWithType:stepType quantity:stepQuantity startDate:startDate endDate:[NSDate date]];
// 写入样本
[self.healthStore saveObject:stepSample withCompletion:^(BOOL success, NSError * _Nullable error) {
if (error) {
NSLog(@"error: %@", error.localizedDescription);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"写入%@",success ? @"成功" : @"失败");
});
}];
}];
}
代码读取:
健康App:
微信运动:
QQ运动:
Keep运动:
支付宝运动:
总结
还记得咱们正常的步数是3288吗!
代码写入10000步后,各个App差异就比较大了。咱们能够在设置中检查哪个第三方App写入了数据:
其次查询HKSource
数据来历,也能够知道当前数据来历一共有四种:
首先是咱们代码获取的,这是能够预见的,究竟咱们只是过滤了用户在健康App手动输入的,对于第三方App写入的数据并没有处理。
健康App也并不是咱们幻想的那样直接将与之前的数值相加,而是做了必定的处理,这也是能够预见的,在第三方写入的时分,在不同的时刻段的重复数据进行优化!
其间QQ运动也飘了,并且奇怪的是再删去了过程一和过程二增加的20000步时,其他的App或多或少都对应更新数据,但QQ好像是将数据本地耐久化了,一直不变;并且笔者测试了多次,有时分QQ的算法跟咱们代码完成的数值一致,即总步数 – 修改的步数,但有的时分却又不一样。不知道是否是刷新的问题?但能够肯定的是QQ的运动步数存在问题。
而Keep仍旧,并没有过滤手动输入和第三方写入的数据。
微信和支付宝仍是稳啊!依然是3288。
咱们也能够用代码输出一切的样本信息,由于篇幅原因,咱们取用前后的截图:
由此咱们能够精确知道在查询时刻内每个样本的采样时刻、来历、数值、方法等。
也能够知道为什么第三方写入步数数据后,健康App没有直接相加的原因。由于咱们写入的时刻段是:00:00:00 ~ 13:54:36,这个时刻段中,iPhone和iWatch都有运动的记载,三者数据或多或少有必定的穿插,甚至是手机和手表在相同的时刻段中的步数也有必定的差异,健康App应该做了必定的优化和算法处理,得到他以为合理的数值。
而微信和支付宝则直接摒弃了一切外来的数据。哪一种处理方法合理,其实咱们试验成果已经很明了了。
不足之处
这点笔者很惋惜,研讨了一个下午的时分也没有澄清微信的优化和算法。有知道的大佬还请不吝赐教,感谢!
最终附录步数数据:
// 时刻段数据(设备 开端时刻 完毕时刻 步数)
iPhone 08:15:47 08:15:49 4
iPhone 08:15:21 08:15:24 5
Apple Watch 08:11:43 08:15:10 265
iPhone 08:07:55 08:15:21 786
iPhone 08:07:08 08:07:55 9
Apple Watch 08:04:02 08:11:43 584
iPhone 07:56:55 08:04:05 194
Apple Watch 07:52:31 07:59:13 254
iPhone 07:46:04 07:55:40 83
iPhone 07:22:51 07:22:56 12
AppleWatch 运动时刻 = 1069.82 s
iPhone 运动时刻 = 1509.27 s
运动总时刻 = 1074.98 s
实在运动步数(总步数 - 修改的步数) = 1115
健康App手动修改的步数 = 0
第三方App写入的步数 = 0