简介

在HeathKit中,能够运用HKHealthStore类来拜访健康App中的数据,如健身记载、养分摄入、睡觉状况等。也能够对这些数据进行读取和同享(即第三方App写入或删去数据到苹果健康App中)。笔者就以获取步数为例,还能够获取身高、体重、睡觉时刻、心率等等。

运用前

在Xcode中, 翻开HealthKit 功用

iOS之HeathKit详解

在info.plist文件中,增加NSHealthShareUsageDescription用于读取数据的描绘和NSHealthUpdateUsageDescription用于写入数据的描绘。

iOS之HeathKit详解

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)

常用的数据类型:

  • 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];

运转:

iOS之HeathKit详解

健康App截图:

iOS之HeathKit详解

微信截图:

iOS之HeathKit详解

QQ截图:

QQ特意分两种状况,授权运用健康App前后两种状况。 未授权截图:

iOS之HeathKit详解

授权后截图:

iOS之HeathKit详解

以上可知QQ获取运动步数的方法是,若用户授权运用健康App,则读取健康App的数据;若用户未授权则运用CMPedometer传感器获取。

Keep截图:

iOS之HeathKit详解

支付宝截图:

iOS之HeathKit详解

iOS之HeathKit详解
总结

多数App优先运用的仍是健康App的数据,其次运用CMPedometer传感器获取运动步数。其间Keep作为一款主打运动的运用,可能有自己的优化或算法也算正常。

过程二

在健康App手动修改输入10000步:

iOS之HeathKit详解

代码读取:

iOS之HeathKit详解

健康App截图:

iOS之HeathKit详解

微信运动截图:

iOS之HeathKit详解

QQ运动截图:

iOS之HeathKit详解

Keep截图:

iOS之HeathKit详解

支付宝运动截图:

iOS之HeathKit详解

总结

在健康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 ? @"成功" : @"失败");
    });
   }];    
 }];
}

代码读取:

iOS之HeathKit详解

健康App:

iOS之HeathKit详解

微信运动:

iOS之HeathKit详解

QQ运动:

iOS之HeathKit详解

Keep运动:

iOS之HeathKit详解

支付宝运动:

iOS之HeathKit详解

总结

还记得咱们正常的步数是3288吗!

代码写入10000步后,各个App差异就比较大了。咱们能够在设置中检查哪个第三方App写入了数据:

iOS之HeathKit详解

其次查询HKSource数据来历,也能够知道当前数据来历一共有四种:

iOS之HeathKit详解

首先是咱们代码获取的,这是能够预见的,究竟咱们只是过滤了用户在健康App手动输入的,对于第三方App写入的数据并没有处理。

健康App也并不是咱们幻想的那样直接将与之前的数值相加,而是做了必定的处理,这也是能够预见的,在第三方写入的时分,在不同的时刻段的重复数据进行优化!

其间QQ运动也飘了,并且奇怪的是再删去了过程一和过程二增加的20000步时,其他的App或多或少都对应更新数据,但QQ好像是将数据本地耐久化了,一直不变;并且笔者测试了多次,有时分QQ的算法跟咱们代码完成的数值一致,即总步数 – 修改的步数,但有的时分却又不一样。不知道是否是刷新的问题?但能够肯定的是QQ的运动步数存在问题。

而Keep仍旧,并没有过滤手动输入和第三方写入的数据。

微信和支付宝仍是稳啊!依然是3288

咱们也能够用代码输出一切的样本信息,由于篇幅原因,咱们取用前后的截图:

iOS之HeathKit详解

iOS之HeathKit详解

由此咱们能够精确知道在查询时刻内每个样本的采样时刻、来历、数值、方法等。

也能够知道为什么第三方写入步数数据后,健康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