iOS 9 中的 Healthkit HKAnchoredObjectQuery 未返回 HKDeletedObject
Healthkit HKAnchoredObjectQuery in iOS 9 not returning HKDeletedObject
我很高兴得知 Apple 在 iOS9 中添加了 HealthKit 中的删除跟踪。所以我建立了一个测试项目来尝试一下。不幸的是,虽然我可以很好地获取新数据,但我没有在回调中获取任何已删除的对象。
我有一个功能正常的 HKAnchoredObjectQuery,它可以跟踪 HKQueryAnchor,并在我通过 Health 应用程序将血糖值添加到 HealthKit 时为我提供新的 HKSamples。但是,当我删除相同数量并重新 运行 应用程序时,HKDeletedObject 始终为空。甚至我同时进行添加和删除。似乎无论我做什么,HKDeletedObject 数组总是空的。但添加工作正常(仅获取自上一个锚点以来添加的样本)。
这是我的代码。它只是2个文件。要重新创建项目,只需创建一个新的 swift 项目,给自己 HealthKit 权利,然后将其复制进去。(注意:当您 运行 它时,每个 运行 只能获得一个更新因此,如果您在 HealthKit 中进行更改,则必须停止并重新启动应用程序以测试回调。)
这是我的 HealthKit 客户端:
//
// HKClient.swift
// HKTest
import UIKit
import HealthKit
class HKClient : NSObject {
var isSharingEnabled: Bool = false
let healthKitStore:HKHealthStore? = HKHealthStore()
let glucoseType : HKObjectType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!
override init(){
super.init()
}
func requestGlucosePermissions(authorizationCompleted: (success: Bool, error: NSError?)->Void) {
let dataTypesToRead : Set<HKObjectType> = [ glucoseType ]
if(!HKHealthStore.isHealthDataAvailable())
{
// let error = NSError(domain: "com.test.healthkit", code: 2, userInfo: [NSLocalizedDescriptionKey: "Healthkit is not available on this device"])
self.isSharingEnabled = false
return
}
self.healthKitStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead){(success, error) -> Void in
self.isSharingEnabled = true
authorizationCompleted(success: success, error: error)
}
}
func getGlucoseSinceAnchor(anchor:HKQueryAnchor?, maxResults:uint, callback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!){
let queryEndDate = NSDate(timeIntervalSinceNow: NSTimeInterval(60.0 * 60.0 * 24))
let queryStartDate = NSDate.distantPast()
let sampleType: HKSampleType = glucoseType as! HKSampleType
let predicate: NSPredicate = HKAnchoredObjectQuery.predicateForSamplesWithStartDate(queryStartDate, endDate: queryEndDate, options: HKQueryOptions.None)
var hkAnchor: HKQueryAnchor;
if(anchor != nil){
hkAnchor = anchor!
} else {
hkAnchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
}
let onAnchorQueryResults : ((HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, NSError?) -> Void)! = {
(query:HKAnchoredObjectQuery, addedObjects:[HKSample]?, deletedObjects:[HKDeletedObject]?, newAnchor:HKQueryAnchor?, nsError:NSError?) -> Void in
var added = [String]()
var deleted = [String]()
if (addedObjects?.count > 0){
for obj in addedObjects! {
let quant = obj as? HKQuantitySample
if(quant?.UUID.UUIDString != nil){
let val = Double( (quant?.quantity.doubleValueForUnit(HKUnit(fromString: "mg/dL")))! )
let msg : String = (quant?.UUID.UUIDString)! + " " + String(val)
added.append(msg)
}
}
}
if (deletedObjects?.count > 0){
for del in deletedObjects! {
let value : String = del.UUID.UUIDString
deleted.append(value)
}
}
if(callback != nil){
callback(source:self, added: added, deleted: deleted, newAnchor: newAnchor, error: nsError)
}
}
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
healthKitStore?.executeQuery(anchoredQuery)
}
let AnchorKey = "HKClientAnchorKey"
func getAnchor() -> HKQueryAnchor? {
let encoded = NSUserDefaults.standardUserDefaults().dataForKey(AnchorKey)
if(encoded == nil){
return nil
}
let anchor = NSKeyedUnarchiver.unarchiveObjectWithData(encoded!) as? HKQueryAnchor
return anchor
}
func saveAnchor(anchor : HKQueryAnchor) {
let encoded = NSKeyedArchiver.archivedDataWithRootObject(anchor)
NSUserDefaults.standardUserDefaults().setValue(encoded, forKey: AnchorKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
这是我的观点:
//
// ViewController.swift
// HKTest
import UIKit
import HealthKit
class ViewController: UIViewController {
let debugLabel = UILabel(frame: CGRect(x: 10,y: 20,width: 350,height: 600))
override func viewDidLoad() {
super.viewDidLoad()
self.view = UIView();
self.view.backgroundColor = UIColor.whiteColor()
debugLabel.textAlignment = NSTextAlignment.Center
debugLabel.textColor = UIColor.blackColor()
debugLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
debugLabel.numberOfLines = 0
self.view.addSubview(debugLabel)
let hk = HKClient()
hk.requestGlucosePermissions(){
(success, error) -> Void in
if(success){
let anchor = hk.getAnchor()
hk.getGlucoseSinceAnchor(anchor, maxResults: 0)
{ (source, added, deleted, newAnchor, error) -> Void in
var msg : String = String()
if(deleted?.count > 0){
msg += "Deleted: \n" + (deleted?[0])!
for s in deleted!{
msg += s + "\n"
}
}
if (added?.count > 0) {
msg += "Added: "
for s in added!{
msg += s + "\n"
}
}
if(error != nil) {
msg = "Error = " + (error?.description)!
}
if(msg.isEmpty)
{
msg = "No changes"
}
debugPrint(msg)
if(newAnchor != nil && newAnchor != anchor){
hk.saveAnchor(newAnchor!)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.debugLabel.text = msg
})
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
注意:我知道 Apple 建议使用 HKObserverQuery 进行设置。我最初是在 Xamarin 项目中这样做的,行为是相同的(没有发送 HKDeletedObjects)。因此,当使用 swift 进行尝试时,为了简单起见,我省略了 HKObserverQuery。
在实例化查询时删除谓词 (predicate: nil),您将看到更多结果,包括已删除的结果。第一次 运行 这个(在保存 HKQueryAnchor 之前)你会得到所有的结果,所以你可能想做一些事情来过滤那些;但随后执行的查询将使用您保存的锚点,因此您只会看到自该保存的锚点以来的更改。
您可能还想在执行之前对查询设置 updateHandler 属性。这会将查询设置为 运行 在后台连续调用更新处理程序,只要有更改(添加或删除)。 getGlucoseSinceAnchor(...) 末尾的代码看起来像
...
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
anchoredQuery.updateHandler = onAnchorQueryResults
healthKitStore?.executeQuery(anchoredQuery)
...
我很高兴得知 Apple 在 iOS9 中添加了 HealthKit 中的删除跟踪。所以我建立了一个测试项目来尝试一下。不幸的是,虽然我可以很好地获取新数据,但我没有在回调中获取任何已删除的对象。
我有一个功能正常的 HKAnchoredObjectQuery,它可以跟踪 HKQueryAnchor,并在我通过 Health 应用程序将血糖值添加到 HealthKit 时为我提供新的 HKSamples。但是,当我删除相同数量并重新 运行 应用程序时,HKDeletedObject 始终为空。甚至我同时进行添加和删除。似乎无论我做什么,HKDeletedObject 数组总是空的。但添加工作正常(仅获取自上一个锚点以来添加的样本)。
这是我的代码。它只是2个文件。要重新创建项目,只需创建一个新的 swift 项目,给自己 HealthKit 权利,然后将其复制进去。(注意:当您 运行 它时,每个 运行 只能获得一个更新因此,如果您在 HealthKit 中进行更改,则必须停止并重新启动应用程序以测试回调。)
这是我的 HealthKit 客户端:
//
// HKClient.swift
// HKTest
import UIKit
import HealthKit
class HKClient : NSObject {
var isSharingEnabled: Bool = false
let healthKitStore:HKHealthStore? = HKHealthStore()
let glucoseType : HKObjectType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!
override init(){
super.init()
}
func requestGlucosePermissions(authorizationCompleted: (success: Bool, error: NSError?)->Void) {
let dataTypesToRead : Set<HKObjectType> = [ glucoseType ]
if(!HKHealthStore.isHealthDataAvailable())
{
// let error = NSError(domain: "com.test.healthkit", code: 2, userInfo: [NSLocalizedDescriptionKey: "Healthkit is not available on this device"])
self.isSharingEnabled = false
return
}
self.healthKitStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead){(success, error) -> Void in
self.isSharingEnabled = true
authorizationCompleted(success: success, error: error)
}
}
func getGlucoseSinceAnchor(anchor:HKQueryAnchor?, maxResults:uint, callback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!){
let queryEndDate = NSDate(timeIntervalSinceNow: NSTimeInterval(60.0 * 60.0 * 24))
let queryStartDate = NSDate.distantPast()
let sampleType: HKSampleType = glucoseType as! HKSampleType
let predicate: NSPredicate = HKAnchoredObjectQuery.predicateForSamplesWithStartDate(queryStartDate, endDate: queryEndDate, options: HKQueryOptions.None)
var hkAnchor: HKQueryAnchor;
if(anchor != nil){
hkAnchor = anchor!
} else {
hkAnchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
}
let onAnchorQueryResults : ((HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, NSError?) -> Void)! = {
(query:HKAnchoredObjectQuery, addedObjects:[HKSample]?, deletedObjects:[HKDeletedObject]?, newAnchor:HKQueryAnchor?, nsError:NSError?) -> Void in
var added = [String]()
var deleted = [String]()
if (addedObjects?.count > 0){
for obj in addedObjects! {
let quant = obj as? HKQuantitySample
if(quant?.UUID.UUIDString != nil){
let val = Double( (quant?.quantity.doubleValueForUnit(HKUnit(fromString: "mg/dL")))! )
let msg : String = (quant?.UUID.UUIDString)! + " " + String(val)
added.append(msg)
}
}
}
if (deletedObjects?.count > 0){
for del in deletedObjects! {
let value : String = del.UUID.UUIDString
deleted.append(value)
}
}
if(callback != nil){
callback(source:self, added: added, deleted: deleted, newAnchor: newAnchor, error: nsError)
}
}
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
healthKitStore?.executeQuery(anchoredQuery)
}
let AnchorKey = "HKClientAnchorKey"
func getAnchor() -> HKQueryAnchor? {
let encoded = NSUserDefaults.standardUserDefaults().dataForKey(AnchorKey)
if(encoded == nil){
return nil
}
let anchor = NSKeyedUnarchiver.unarchiveObjectWithData(encoded!) as? HKQueryAnchor
return anchor
}
func saveAnchor(anchor : HKQueryAnchor) {
let encoded = NSKeyedArchiver.archivedDataWithRootObject(anchor)
NSUserDefaults.standardUserDefaults().setValue(encoded, forKey: AnchorKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
这是我的观点:
//
// ViewController.swift
// HKTest
import UIKit
import HealthKit
class ViewController: UIViewController {
let debugLabel = UILabel(frame: CGRect(x: 10,y: 20,width: 350,height: 600))
override func viewDidLoad() {
super.viewDidLoad()
self.view = UIView();
self.view.backgroundColor = UIColor.whiteColor()
debugLabel.textAlignment = NSTextAlignment.Center
debugLabel.textColor = UIColor.blackColor()
debugLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
debugLabel.numberOfLines = 0
self.view.addSubview(debugLabel)
let hk = HKClient()
hk.requestGlucosePermissions(){
(success, error) -> Void in
if(success){
let anchor = hk.getAnchor()
hk.getGlucoseSinceAnchor(anchor, maxResults: 0)
{ (source, added, deleted, newAnchor, error) -> Void in
var msg : String = String()
if(deleted?.count > 0){
msg += "Deleted: \n" + (deleted?[0])!
for s in deleted!{
msg += s + "\n"
}
}
if (added?.count > 0) {
msg += "Added: "
for s in added!{
msg += s + "\n"
}
}
if(error != nil) {
msg = "Error = " + (error?.description)!
}
if(msg.isEmpty)
{
msg = "No changes"
}
debugPrint(msg)
if(newAnchor != nil && newAnchor != anchor){
hk.saveAnchor(newAnchor!)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.debugLabel.text = msg
})
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
注意:我知道 Apple 建议使用 HKObserverQuery 进行设置。我最初是在 Xamarin 项目中这样做的,行为是相同的(没有发送 HKDeletedObjects)。因此,当使用 swift 进行尝试时,为了简单起见,我省略了 HKObserverQuery。
在实例化查询时删除谓词 (predicate: nil),您将看到更多结果,包括已删除的结果。第一次 运行 这个(在保存 HKQueryAnchor 之前)你会得到所有的结果,所以你可能想做一些事情来过滤那些;但随后执行的查询将使用您保存的锚点,因此您只会看到自该保存的锚点以来的更改。
您可能还想在执行之前对查询设置 updateHandler 属性。这会将查询设置为 运行 在后台连续调用更新处理程序,只要有更改(添加或删除)。 getGlucoseSinceAnchor(...) 末尾的代码看起来像
...
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
anchoredQuery.updateHandler = onAnchorQueryResults
healthKitStore?.executeQuery(anchoredQuery)
...