iOS 9 中的 Healthkit HKAnchoredObjectQuery 不返回 HKDeletedObject

Posted

技术标签:

【中文标题】iOS 9 中的 Healthkit HKAnchoredObjectQuery 不返回 HKDeletedObject【英文标题】:Healthkit HKAnchoredObjectQuery in iOS 9 not returning HKDeletedObject 【发布时间】:2015-09-29 21:11:43 【问题描述】:

我很高兴得知 Apple 在 ios 9 的 HealthKit 中添加了删除跟踪功能。所以我建立了一个测试项目来尝试一下。不幸的是,虽然我可以很好地获取新数据,但我的回调中没有得到任何已删除的对象。

我有一个功能正常的 HKAnchoredObjectQuery,它跟踪 HKQueryAnchor 并在我通过 Health 应用程序将 BloodGlucose 数量添加到 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。

【问题讨论】:

【参考方案1】:

在实例化查询时删除谓词(谓词:nil),您会看到更多结果,包括已删除的结果。第一次运行时(在 HKQueryAnchor 保存之前),您将获得所有结果,因此您可能需要做一些事情来过滤这些结果;但后续执行查询将使用您保存的锚点,因此您只会看到自保存的锚点以来的更改。

您可能还想在执行前设置查询的 updateHandler 属性。这会将查询设置为在有更改(添加或删除)时调用更新处理程序的后台连续运行。 getGlucoseSinceAnchor(...) 末尾的代码看起来像

...
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
anchoredQuery.updateHandler = onAnchorQueryResults
healthKitStore?.executeQuery(anchoredQuery)
...

【讨论】:

谢谢。这很有效,但是如果我想为已删除的对象进行回调,我无法指定谓词,这让我感到非常难过。如果我只给它一个开始日期而没有结束日期,它甚至会失败。相反,我必须始终使用 nil 谓词并从所有来源获取所有结果,然后在事后进行解析。如果谓词仅适用于添加的条目,那对我来说似乎是错误的。 经过进一步调查,源谓词确实适用于删除。我的理论是这样的:getPredicateForSamples 按日期查询 HKSamples。 HKDeletedObjects 继承自 HKObject,而不是 HKSample。因此,我怀疑由于 getPredicateForSamples() 过滤了日期范围内的 HKSamples,它会跳过 HKDeletedObjects。现在,如果我使用 getPredicateForObjects() 过滤源,它会过滤 HKObjects,而不是 HKSamples。因此,它返回 HKSample 和 HKDeletedObject,因为这两个类都继承自 HKObject。我不喜欢它,但似乎就是这样。 当我看到你的评论时,我正要深入研究一下。你的理论很有道理。 看起来除了在没有保存锚时第一次执行锚定查询之外,您还可以在没有日期范围谓词的情况下查看您正在寻找的更改 -添加和删​​除 - 并且只有从那时起的那些,因为您正在保存锚点并在/当您必须重新开始时使用它。

以上是关于iOS 9 中的 Healthkit HKAnchoredObjectQuery 不返回 HKDeletedObject的主要内容,如果未能解决你的问题,请参考以下文章

iOS Apple HealthKit 仅 Apple Watch 步数数据

iOS8 应用程序使用 HealthKit 在 Xcode6.0.1 上崩溃

iOS8在iPad Air上使用HealthKit时如何修复“此设备上的健康数据不可用”

iOS8在iPad Air上使用HealthKit时如何修复“此设备上的健康数据不可用”

如何通过 healthkit 使用 Apple Watch 心率更新 iOS 应用程序? (HealthKit 同步)

iOS 从 HealthKit 读取数据