gpt4 book ai didi

ios - 为什么 HKAnchoredObjectQuery with enableBackgroundDeliveryForType 在应用程序处于后台时不总是触发?

转载 作者:塔克拉玛干 更新时间:2023-11-02 09:39:51 24 4
gpt4 key购买 nike

我正在做一些实验以熟悉 HKAnchoredObjectQuery 并在我的应用程序处于非事件状态时获取结果。我启动应用程序,切换到 Apple Health,输入血糖结果;有时会立即调用结果处理程序(如打印到控制台所证明的那样),但其他时候直到我切换回我的应用程序才调用处理程序。删除的结果和添加的结果也是如此。有人有任何指导吗?

大部分代码来自 thedigitalsean 的一个问题在此处进行调整以在应用程序处于后台并登录到控制台时获取更新。请参阅:Healthkit HKAnchoredObjectQuery in iOS 9 not returning HKDeletedObject

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)
}
}

// remove predicate to see deleted objects
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)

// added - query should be always running
anchoredQuery.updateHandler = onAnchorQueryResults

// added - allow query to pickup updates when app is in backgroun
healthKitStore?.enableBackgroundDeliveryForType(sampleType, frequency: .Immediate) {
(success, error) in
if (!success) {print("enable background error")}
}

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()
}
}


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.
}
}

我还在各种应用程序状态更改时添加了 print()。控制台日志示例(这是在 XCode 的 iPhone 6s 设备上运行)显示处理程序有时在我进入后台但在重新进入前台之前被调用,而其他时候仅在重新进入前台之后被调用。

app did become active
"No changes"
app will resign active
app did enter background
app will enter foreground
"Added: E0340084-6D9A-41E4-A9E4-F5780CD2EADA 99.0\n"
app did become active
app will resign active
app did enter background
"Added: CEBFB656-0652-4109-B994-92FAA45E6E55 98.0\n"
app will enter foreground
"Added: E2FA000A-D6D5-45FE-9015-9A3B9EB1672C 97.0\n"
app did become active
app will resign active
app did enter background
"Deleted: \nD3124A07-23A7-4571-93AB-5201F73A4111D3124A07-23A7-4571-93AB-5201F73A4111\n92244E18-941E-4514-853F-D890F4551D76\n"
app will enter foreground
app did become active
app will resign active
app did enter background
app will enter foreground
"Added: 083A9DE4-5EF6-4992-AB82-7CDDD1354C82 96.0\n"
app did become active
app will resign active
app did enter background
app will enter foreground
"Added: C7608F9E-BDCD-4CBC-8F32-94DF81306875 95.0\n"
app did become active
app will resign active
app did enter background
"Deleted: \n15D5DC92-B365-4BB1-A40C-B870A48A70A415D5DC92-B365-4BB1-A40C-B870A48A70A4\n"
"Deleted: \n17FB2A43-0828-4830-A229-7D7DDC6112DB17FB2A43-0828-4830-A229-7D7DDC6112DB\n"
"Deleted: \nCEBFB656-0652-4109-B994-92FAA45E6E55CEBFB656-0652-4109-B994-92FAA45E6E55\n"
app will enter foreground
"Deleted: \nE0340084-6D9A-41E4-A9E4-F5780CD2EADAE0340084-6D9A-41E4-A9E4-F5780CD2EADA\n"
app did become active

最佳答案

我建议使用 HKObserverQuery 并仔细设置它。

当您启用后台传送时,有一种算法可以监视您如何以及何时调用 HKObserverQuery 的“完成”处理程序。不幸的是,这方面的细节含糊不清。 Apple Dev 论坛上有人将其称为“3 次罢工”规则,但 Apple 尚未发布任何我能找到的有关其行为的文档。

https://forums.developer.apple.com/thread/13077

我注意到的一件事是,如果您的应用程序使用 HKObserverQuery 响应后台传送,创建 HKAnchoredObjectQuery,并在该 HKAnchoredObjectQuery 中设置 UpdateHandler,则此 UpdateHandler 通常会导致多次触发回调。我怀疑也许因为这些额外的回调是在你已经告诉苹果你已经完成你的工作以响应后台交付之后执行的,所以你多次调用完成处理程序,也许他们给你一些“积分”并调用你不良行为较少见。

通过执行以下操作,我在获得一致的回调方面取得了最大的成功:

  1. 使用 ObserverQuery 并确保“完成”处理程序的调用在您的工作结束时被调用一次。
  2. 在后台运行时不在我的 HKAnchoredObjectQuery 中设置更新处理程序(有助于实现 1)。
  3. 专注于使我的查询处理程序、AppDelegate 和 ViewController 尽可能快。我注意到,当我将所有回调缩减为打印语句时,来自 HealthKit 的回调会立即出现并且更加一致。所以这说明 Apple 肯定会关注执行时间。因此,请尽可能静态地声明事物并关注速度。

从那以后我就开始使用 Xamarin.iOS 的原始项目,而不是 swift,所以我没有跟上我最初发布的代码。但这里是该代码的更新(且未经测试)版本,应该考虑这些更改(速度改进除外):

//
// 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 startBackgroundGlucoseObserver( maxResultsPerQuery: Int, anchorQueryCallback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!)->Void {
let onBackgroundStarted = {(success: Bool, nsError : NSError?)->Void in
if(success){
//Background delivery was successfully created. We could use this time to create our Observer query for the system to call when changes occur. But we do it outside this block so that even when background deliveries don't work,
//we will have the observer query working when are in the foreground at least.
} else {
debugPrint(nsError)
}


let obsQuery = HKObserverQuery(sampleType: self.glucoseType as! HKSampleType, predicate: nil) {
query, completion, obsError in

if(obsError != nil){
//Handle error
debugPrint(obsError)
abort()
}

var hkAnchor = self.getAnchor()
if(hkAnchor == nil) {
hkAnchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
}

self.getGlucoseSinceAnchor(hkAnchor, maxResults: maxResultsPerQuery, callContinuosly:false, callback: { (source, added, deleted, newAnchor, error) -> Void in
anchorQueryCallback(source: self, added: added, deleted: deleted, newAnchor: newAnchor, error: error)

//Tell Apple we are done handling this event. This needs to be done inside this handler
completion()
})
}

self.healthKitStore?.executeQuery(obsQuery)
}

healthKitStore?.enableBackgroundDeliveryForType(glucoseType, frequency: HKUpdateFrequency.Immediate, withCompletion: onBackgroundStarted )
}

func getGlucoseSinceAnchor(anchor:HKQueryAnchor?, maxResults:Int, callContinuosly:Bool, callback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!){

let sampleType: HKSampleType = glucoseType as! HKSampleType
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: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
if(callContinuosly){
//The updatehandler should not be set when responding to background observerqueries since this will cause multiple callbacks
anchoredQuery.updateHandler = 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()
}
}

关于ios - 为什么 HKAnchoredObjectQuery with enableBackgroundDeliveryForType 在应用程序处于后台时不总是触发?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33002468/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com