Request from Apple Watch takes an absurd amount of time - ios

In my apple watch application I am making a request to location services and to a web service. Performing both these tasks on the iPhone takes roughly 2 seconds, at most. However, when I am making the same requests from the Apple watch it takes more than 10 seconds before I get a reply...unless the iPhone application is already running in which case the request takes about 2 seconds.
I figure there is something wrong with my background task in application:handleWatchKitExtensionRequest:reply: but I cannot figure out what. All hints are appreciated.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier identifier = UIBackgroundTaskInvalid;
dispatch_block_t endBlock = ^ {
if (identifier != UIBackgroundTaskInvalid) {
[application endBackgroundTask:identifier];
}
identifier = UIBackgroundTaskInvalid;
};
identifier = [application beginBackgroundTaskWithExpirationHandler:endBlock];
reply = ^(NSDictionary *replyInfo) {
reply(replyInfo);
endBlock();
};
// Call location services with completion block ^{
// Use the location and make request to web service
// with completion ^{
// reply(#{#"info" : #"here be dragons"});
// };
//};
}

In order to save power, the Apple watch limits the update frequency for location services (a major power hog). When you open the iPhone application, you prioritize the task you're running, and the Apple watch will update its location more often.
You can try to force the application to update faster by sending a "wake up" ping: send an empty/dummy call to the application and reply with a dummy reply. This should wake up the connection for a while, and your second call might be processed faster, although I am unsure of whether this "wake up" ping will prioritize location services.
Last thing, IMHO 10 seconds isn't really an "absurd" amount of time.

Related

Wake Application from Suspended state on Beacon Enter taking too long

I have an application that monitors for iBeacons. When the application terminates from a suspended state and then enters a beacon region that it is monitoring for, it can sometimes take a very long time (sometimes up to 1 minute) for the application to wake up (call didEnterRegion or didExitRegion). Is there anything I can do about this? Here is the code I am using when the application enters the background
- (void)extendBackgroundRunningTime {
if (_backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return;
}
// Attempt to extend background time by initializing a placeholder background thread
__block Boolean self_terminate = YES;
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBISBeaconManagerKeyBackgroundTask expirationHandler:^{
// Background task expired, completion
if (self_terminate) {
NSLog(#"Application Terminated");
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Background task started
});
}
A few thoughts:
The code shown in the question extends background beacon ranging time. It does not do anything to affect beacon monitoring (monitoring APIs are what provide didEnterRegion and didExitRegion callbacks).
Monitoring response times can vary quite a bit. Best case scenario, didEnterRegion callbacks will fire within a second or two of the beacon being in range. The didExitRegion event is always delayed by an additional 30 seconds (that's how long iOS waits before deciding a beacon is no longer visible.)
The best case scenario times are not met if all 30 Bluetooth hardware acceleration slots are taken. These slots are across all apps on the phone. If your app tries to monitor a beacon region when all slots are taken, detection times fall back to software backup cycles and can take 15 minutes.
See here for more info:
http://developer.radiusnetworks.com/2014/03/12/ios7-1-background-detection-times.html
http://developer.radiusnetworks.com/2015/04/21/max-beacon-regions-ios.html

Load data asynchronously after notification on apple watch

I'm receiving a notification that has, among other things, an ID in its payload. This ID is simply there so I can fetch the actual object on the server.
Right now, I only communicate using the AppDelegate HandleWatchKitExtensionRequest, and the .OpenParentApplication from the Watchkit.
Using that, if I don't make any webservice calls, it's all fine. Once I try to call the server, I get an error saying that the parent App never calls reply().
So obviously I'm thinking that the async calls makes it fail.
What's the best way to achieve this? I have a feeling I'm doing this wrong but I have no other real solution from what I've found online.
Right now it looks like this
[Watch] Remote Notification received
[Watch] Open parent application with ID in dictionary
[Parent] Call webservice (Fails here)
[Parent] Send results to the watch (some strings and an image)
Note : I'm using Xamarin, but any Obj-C/Swift code will suffice, and I need to be compatible with WatchOS 1, therefore I can't really use WCSession/WatchConnectivity framework, can I?
been in very same situation, and hope following will help you. So, when watchkit app calls the main app, begin background task, load the things you are loading, and end the bg task. Like this:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];
//load the things here.
reply(#{#"hehe":#"hehe123"});
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 10. ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
} );
}
Hope this was useful.

Perform NSLog when App is Sent to Background

I have a seemingly simple task that I am trying to do. When a user sends my application to the background, I would like to conduct an NSLog to say "app sent to background". I am trying to work with the NSNotificationCenter like What's the best way to detect when the app is entering the background for my view?, but I am unable to get it to work.
Is it not possible to perform an action right when the user sends the app to the background? Any help would be great!
Thanks!
You may use NSLog in method of
- (void)applicationDidEnterBackground:(UIApplication *)application
Your AppDelegate.m file.
Where you can send NSNotification for any task. You can not perform any task when app is in background.
You need to implement the following method in AppDelegate:
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"app sent to background");
}
Apple's documentation:
Your implementation of this method has approximately five seconds to
perform any tasks and return. If you need additional time to perform
any final tasks, you can request additional execution time from the
system by calling beginBackgroundTaskWithExpirationHandler:.
You can do a 10 min background activity in pre iOS7 like save some thing to your server but in order to wind up when background activity is about to be closed you need to use this in your - (void)applicationDidEnterBackground:(UIApplication *)application
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
For more detail look here
the summary is
your - (void)applicationDidEnterBackground:(UIApplication *)application method must return within 5 seconds or else your app will be purged.
you can how ever ask for more time using the above method the time allowed in practice is 10 mins

NSURLSession vs Background Fetch

Ok, so I was looking at the SimpleBackgroundFetch example project, and it uses the following in the App Delegate:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:someTimeInSeconds];
//^this code is in didFinishLaunchingWithOptions
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//do something + call completionHandler depending on new data / no data / fail
}
So, basically I assume, that I call my app's server here, to get some data.
But then I saw the NSURLSession docs, and it had methods like these
– downloadTaskWithURL:
and it said the following:
This API provides a rich set of delegate methods for supporting
authentication and gives your app the ability to perform background
downloads when your app is not running or, in iOS, while your app is
suspended.
So what's the difference between these two APIs? And what should I use if I want to download some data from my app's server every now and again?
I just wasn't sure about the difference between the two, so I just thought I should get my doubts clarified here. Go StackOverflow!
These are completely different things.
Background Fetch: System launches your app at some time (heuristics) and your job is to start downloading new content for user.
NSURLSession: Replacement for NSURLConnection, that allows the downloads to continue after the app is suspended.
The application delegate is for storing the completion handler so you can call it when your download is finished.
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
NSLog(#"Handle events for background url session");
self.backgroundSessionCompletionHandler = completionHandler;
}
and call the handler
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
WebAppDelegate *appDelegate = (WebAppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.backgroundSessionCompletionHandler) {
void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;
appDelegate.backgroundSessionCompletionHandler = nil;
completionHandler();
}
NSLog(#"All tasks are finished");
}
NSURLSession:Allows to uploading and downloading in the background mode and suspend mode of application
Background Fetch:Happens according to volume of the data and duration of previous data transferring process.Only last for 30s.
So you confirm a background URLSession endowed with a delegate should be called, while a normal dataTask with block may not be?

If app sends a http request during termination, will the server receive it?

I want to know that if my iOS app is terminating, if I send a HTTP request informing the server that the user is going offline, will the server get this request?
My code is below:
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSMutableDictionary *para = [[NSMutableDictionary alloc]initWithCapacity:0];
[para setObject:[[NSUserDefaults standardUserDefaults] valueForKey:#"UserBindId"] forKey:#"unionId"];
[para setObject:#(0) forKey:#"OnlineState"];
[Post updateOnlineWithParameters:para withBlock:^(NSDictionary *commitResult, NSError *error) {
NSLog(#"applicationWillTerminate = %#", commitResult);
}];
}
See Executing Finite-Length Tasks for an example of how to have an app request a little extra time (max 3 minutes, which should be more than adequate for your purposes) when the app enters background mode.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"GoingOffline" expirationHandler:^{
// Well, we ran out of time before network request finished ... handle that however you want
// but tell the OS that we understand, and that the background task is done
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
NSDictionary *para = #{#"unionID" : [[NSUserDefaults standardUserDefaults] valueForKey:#"UserBindId"],
#"OnlineState" : #0};
[Post updateOnlineWithParameters:para withBlock:^(NSDictionary *commitResult, NSError *error) {
// when done, tell the OS that the background task is done
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
I tried to simplify your parameters a bit (no need for mutable dictionary ... you can use dictionary literal), so adjust that as suits your needs. But hopefully you can follow the basic idea here, if you want to do something when the app enters background, you must request permission for that.
You can't provide a failsafe user online/offline status capability from the client side, using http requests. As Rob said, if the app is terminated (force quit or crashes e.t.c.), theres nothing you can do. The way to provide this kind of feature is to use a web socket, which is a "constant" two way communication between the phone and server. This way on the server side, you can check whether the connection was lost, which indicates that the client has gone offline.
To create such a service yourself would take quite a long time. Luckily Firebase offer such software with a Backend as a Service. It's a godsend. This page describes how to manage offline and online status. It's as easy as pie.

Resources