I use the code below to have my app detect updates to HealthKit data in the background. Will the init method of my AppDelegate be called when this code is run in the background? What methods in the AppDelegate will be called? If someone can provide documentation about the application lifecycle of background code, that will be much appreciated!
[healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
if (success) {
HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:type
predicate:nil
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if (!error) {
[self retrieveHealthDataWithCompletionHandler:completionHandler];
}
}];
[healthStore executeQuery:observerQuery];
}
A bit late but hopefully, it would still help you or anyone else who reach here..
When your app delegate’s application:didFinishLaunchingWithOptions: method is being called you can assume the app launches. Thats why Apple recommend you'll register any observer queries you'd like to have inside that method.
When there will be new data of the type you registered for, HealthKit will wake your app. (So far you still don't know anything about any new data.) Once your app finish its launching it will call the beloved app delegate’s application:didFinishLaunchingWithOptions: method, which as stated before, should contain the code of registering the observer queries.
Once you register your queries, next thing will be getting an update about new data (this is the purpose of observer queries).
Getting the update about something new in HealthKit doesn't contain the data itself. Thats why in the updateHandler of the observer query you should initiate anther query - a more specific one that will fetch the wanted data.
That's it. I would make some changes in the code you provided in order for it to work:
[healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
if (success) {
//Nothing much to do here
}
}];
HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:type
predicate:nil
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if (!error) {
//Create and execute a query about the sample type.
// Within the completion handler of the new query, don't forget to call completionHandler()
}
}];
[healthStore executeQuery:observerQuery];
You can find more details here:
Receiving Background Deliveries
Apps can also register to receive updates while in the background by calling the HealthKit store’s enableBackgroundDeliveryForType:frequency:withCompletion: method. This method registers your app for background notifications. HealthKit wakes your app whenever new samples of the specified type are saved to the store. Your app is called at most once per time period defined by the frequency you specified when registering.
As soon as your app launches, HealthKit calls the update handler for any observer queries that match the newly saved data. If you plan on supporting background delivery, set up all your observer queries in your app delegate’s application:didFinishLaunchingWithOptions: method. By setting up the queries in application:didFinishLaunchingWithOptions:, you ensure that the queries are instantiated and ready to use before HealthKit delivers the updates.
After your observer queries have finished processing the new data, you must call the update’s completion handler. This lets HealthKit know that you have successfully received the background delivery.
Related
This case is happening on only one particular test device (xs-max). The other devices we could not replicate this.
The completion block for fetchRecordWithID isn't executing no matter how long I wait.
Here is the code I call on tap of button.
//recordID is not nil
[[[CKContainer defaultContainer] privateCloudDatabase]fetchRecordWithID:recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) {
if (record) {
//save record
}else{
NSLog(#"%#",error);
}
}];
I couldn't debug this as the execution never reaches the block.
Any case this might happen ?
I am not sure if this is the case, but the device iCloud settings are not loaded properly.
We had to restart the device which prompted to re-validate the apple ID in the settings.
Once the validation is done, the icloud settings are shown as expected. And there after, the code executed as expected.
Adding screenshot for reference.
I'm trying to do a pretty basic thing: set up an HKObserverQuery so I can know when various data points are changed (I've made sure that the user has authorized the app to use the data point in question.) For whatever reason, I can get the query to fire every time the app is launched, but it does not fire when I close the app, go into the Health app, and manually update the data point. I've done a fair amount of searching and haven't been able to successfully use the code that others have posted, code that they say works for them.
I'm two weeks into Cocoa/Objective C development, so I'm sure I'm missing something obvious, but I can't see what it is. Any guidance here would be great, even if it's just advice on debugging. Since the app itself is closed and I'm not getting anything that it might log out in the console, I don't really have any visibility into what's happening.
The code that I'm using for the observer query is below:
HKQuantityType *heartRate = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
[self.healthStore enableBackgroundDeliveryForType:heartRate frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"observing heart rate");
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.bodbot.com/Scripts/wearable_heartrate_changed.php"]];
}else{
NSLog(#"FAILED observing heart rate");
}
}];
HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:heartRate predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.bodbot.com/Scripts/wearable_heartrate_changed.php"]];
}];
[self.healthStore executeQuery:query];
Thanks!
I have found, empirically (not from documentation), that the Observer Query does NOT fire when running in the simulator but it DOES fire when running on device. And I do not have the background modes capability turned on.
When you suspend an app on iOS, by default it stops running unless it has taken a background task assertion or has a background mode entitlement. The app cannot receive notifications when it is not running. HealthKit has a feature which can wake your app in the background when there are new samples of a particular type, though. See the Managing Background Delivery documentation for HKHealthStore. Use that in conjunction with HKObserverQuery to be notified whenever there is new data, even when your app is not already running.
I am using the following:
Firebase *fb =[[Firebase alloc] initWithUrl:url];
[fb setValue:d withCompletionBlock:^(NSError *error, Firebase *ref) {
if (error) {
// bad news
} else {
}
}];
This seems to work great IF you have a connection, if not it seems the callback is never called. If that is the case do I then need to wrap this whole thing in a connectedRef? Seems like alot of extra work when I would guess the completion block would just fail with an error status of not online.
Anyone else having this issue?
The idea behind Firebase is it synchronizes data for you. It's more than just a simple request / response system. So if you do a setValue while offline, Firebase will hold onto that data until you are online, and then it'll do the setValue at that time (and then the completion block will be called).
So the behavior you're seeing is expected. If you only want to do the setValue if you're online, then yes, you'll need to use a .info/connected observer. But you could still run into issues if for instance you go offline at the moment you try to do the setValue or something along those lines. In general it's better to just do the setValue and let Firebase take care of it for you.
I guess this is really a feature request to Google, but I'm curious if anyone knows a work around. I'd like to execute a synchronous query request to a GAE Endpoints api.
In Android executing a request is synchronous. Then you put it into an AsyncTask to make it work in the background.
In iOS executing a request is asynchronous. You simply pass in a callback block.
I'm converting an Android app into an iOS app and it'd be really nice if they used the same mechanism. For example there are times when I WANT a synchronous query. It just makes my code easier and I know to put it on a background thread.
So my question is this... is there any way (hacky or not) to block until the iOS query completes?
You can wait on the call to finish with code with a timeout using code similar to this. Obviously you wouldn't want to do this on a UI thread but this would ensure your completion handlers run in serial.
NSLog(#"Before API Call");
GTLServiceTicket *apiCall = [apiService executeQuery:query completionHandler:^(GTLServiceTicket *ticket,
GTLHelloworldHelloGreeting *object,
NSError *error) {
NSLog(#"Starting completion handler");
NSArray *greetings = [NSArray arrayWithObjects: object, nil];
greetingsRetrievedFromAPI = greetings;
[self performSegueWithIdentifier: #"DisplayGreetings" sender: self];
NSLog(#"Ending completion handler");
}];
[apiService waitForTicket:apiCall timeout:100000 fetchedObject:nil error:nil];
NSLog(#"After completion handler");
So the objective I'm trying to achieve
is a syncing process that is supposed to be completed in the background using AFNetworking and Magical Record, but causes a perpetual hang when a view controller hooked up to a NSFetchedResultsController is currently open or has been opened (but popped).
The app syncs the first time the user opens the phone and then uses the data always in the Core Data persistance store via the Magical Record framework. Then when the user wants to make sure that there data is the most recent version, they go into settings and click "Re-Sync", which causes the following code to execute:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[[CoreDataOperator sharedOperator] commenceSync:self];
});
This starts the syncing process using the singleton CoreDataOperator (subclass of NSObject -- maybe it should be NSOperation?), which fires off the following code:
[[ApiClient sharedClient] getDataRequest];
which then then fires off this bad boy in the singleton AFHTTPClient subclass:
[[ApiClient sharedClient] postPath:url parameters:dict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[request.sender performSelector:request.succeeded withObject:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[request.sender performSelector:request.failed withObject:response];
}
];
which effectively says: AFHTTPClient post this infomation and when it succeeds, pass back the information to the provided selector (I understand that this is generic, but the request isn't the issue)
NOW, AFNetworking is coded such that all completion selectors (in this case specifically, success and failure) are called on the main thread; so in order to prevent blocking the main thread, the code that processes the response and prepares it for saving is sent back into the background thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
id responseData;
[self clearAndSaveGetData:responseData];
});
This then calls the function that causes the saving using the Magical Record framework (still in background thread):
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
[DATAENTITY truncateAllInContext:localContext];
<!--- PROCESS DATA INTO DATAENTITY -->
[localContext saveNestedContexts];
I chose saveNestedContexts because since I am working in the background, I wanted it pushed all the way up to the defaults contexts, which I'm assuming is a parent context? (but this so far hasn't been an issue).
Now, this data can become thousands and thousands of rows, so I'm using a NSFetchedResultsController to access this data safely and efficiently, and they are used in a different view controller than the settings or main page.
Here are three cases:
The ViewController containing the FRC has not been accessed (not visible and hasn't been visible before) - the background sync works flawlessly, minus a little lag due to the saving up the stack.
The ViewController containing the FRC has been accessed and is currently visible - background sync process HANGS due to what appears to be the FRC receiving context updates.
The ViewController containing the FRC has been previously access, but it isn't currently visible (the VC was popped using the following code: [self.navigationController popViewControllerAnimated:YES];) AND the FRC is set to nil in ViewDidUnload - background sync process HANGS due to what appears to be the FRC receiving context updates (just like in case 2).
[PS: after hanging for a few minutes, I kill the debugger and it gives my a SIGKILL on the following code, which is why I'm assuing the FRC is receiving context updates that causes it to hang:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates]; <---- SIGKILL
}
Other important information of note:
I am using 2 separate FRCs, one for normal ALL data and one for searching
I am using a cache for both the FRC and the separate search FRC, which is purged at the appropriate times (before this context update stuff)
The FRCs are fetching in the mainthread (which does cause a slight hang when there is THOUSANDS of rows of data) and I have been looking into fetching in the background, however that is currently not implemented.
The QUESTION(s):
Why is this hanging occuring, where the VC is visible or has been popped?
How can I make it such that the FRC doesn't listen for the save, but uses what it has until the save is completed and then it refreshes the data (unless this is what already occuring and causing the hanging)?
Would implementing a background fetch (because the lag when opening the VC with the FRC to access the thousands of rows of data creates a noticable lag, even with a cache and lessened predicates/section headers - of between 1 and 4 seconds) be viable? Too complex? How can it be done?
Thank you, hope I was detailed enough in my question.
I know this question is old but since this is a common gotcha with MagicalRecord maybe the following will help somebody.
The problem is caused by the fetchRequest of the FRC and the save operation deadlocking each other. The following solved it for me:
Update to the latest MagicalRecord release (2.1 at the time of writing).
Then do all your background saves using the following:
MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// create new objects, update existing objects. make sure you're only
// accessing objects inside localContext
[myObjectFromOutsideTheBlock MR_inContext:localContext]; //is your friend
}
completion:^(BOOL success, NSError *error) {
// this gets called in the main queue. safe to update UI.
}];