handlewatchkitextensionrequest not calling Async call in suspended state - ios

From Apple watch extension, handlewatchkitextensionrequest is not calling async call and returns nill data to apple watch, below are code please help me out.
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if ([[userInfo objectForKey:#"request"] isEqualToString:#"NearestFacility"]) {
[self NearestFacilityClicked];
if (self.ary_WatchKitNearestFacility.count>0) {
NSDictionary *response = #{#"response" : self.ary_WatchKitNearestFacility};
reply(response);
}
}
dispatch_semaphore_signal(sema);
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_after(dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
});
}
-(IBAction)NearestFacilityClicked
{
// ASIFormdatarequest call
[request startAsynchronous];
}

In watch os 2 you need to use session for transferring the data between watch & iPhone.
First you need to initialise the WCSession in viewdidload()
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
then to send the data you can use
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
Then, on the receiving end, you'll need to implement session:didReceiveUserInfo: (Developer documentation). Note, according to Apple's "watchOS2 Transition Guide,"
To begin communication, both your Watch app and your iOS app must have an active WCSession object. Typically, each app creates, configures, and activates a session object at launch time and stores a reference to it in a central location. When you want to send data, you retrieve the session object and call its methods.

Related

Web service call in background mode - iOS

I need to call a web service in every minute and parse the data when app is in background state.
Since the APP uses location service I have enabled background mode for update Location.
I tried calling location update by using a timer background task, but it not working.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:60
target:self.locationManager
selector:#selector(startUpdatingLocation)
userInfo:nil
repeats:YES];
}
Is there any way to implement this with less battery consumption.
I referred this link
I'm not getting which solution is better here.
AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : NSObject {
// Instance member of our background task process
UIBackgroundTaskIdentifier bgTask;
}
#end
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
// bgTask is instance variable
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
if ([application backgroundTimeRemaining] > 1.0) {
// Start background service synchronously
[[BackgroundCleanupService getInstance] run];
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}
There are couple key lines in the above implementation:
The first is the line bgTask = [application beginBackgroundTaskWithExpirationHandler..., which requests additional time to run clean up tasks in the background.
The second is the final code block of the delegate method beginning with dispatch_async. It's basically checking whether there's time left to run an operation via the call [application backgroundTimeRemaining]. In this example, I'm looking to run the background service once but alternatively, you can use a loop checking on the backgroundTimeRemaining on each iteration.
The line [[BackgroundCleanupService getInstance] run] will be a call to our singleton service class, which we'll build right now.
With the app delegate ready to trigger our background task, we now need a service class that will communicate with the web server. In the following example, I'm going to a post a fictitious session key and parse a JSON encoded response. Also, I'm using two helpful libraries to make the request and deserialize the returned JSON, specifically JSONKit and ASIHttpRequest.
BackgroundCleanupService.h
#import <Foundation/Foundation.h>
#interface BackgroundCleanupService : NSObject
+ (BackgroundCleanupService *)getInstance;
- (void)run;
#end
BackgroundCleanupService.m
#import "BackgroundCleanupService.h"
#import "JSONKit.h"
#import "ASIHTTPRequest.h"
#implementation BackgroundCleanupService
/*
* The singleton instance. To get an instance, use
* the getInstance function.
*/
static BackgroundCleanupService *instance = NULL;
/**
* Singleton instance.
*/
+(BackgroundCleanupService *)getInstance {
#synchronized(self) {
if (instance == NULL) {
instance = [[self alloc] init];
}
}
return instance;
}
- (void)run {
NSURL* URL = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.example.com/user/%#/endsession", #"SESSIONKEY"]];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:URL];
[request setTimeOutSeconds:20]; // 20 second timeout
// Handle request response
[request setCompletionBlock:^{
NSDictionary *responseDictionary = [[request responseData] objectFromJSONData];
// Assume service succeeded if JSON key "success" returned
if([responseDictionary objectForKey:#"success"]) {
NSLog(#"Session ended");
}
else {
NSLog(#"Error ending session");
}
}];
// Handle request failure
[request setFailedBlock:^{
NSError *error = [request error];
NSLog(#"Service error: %#", error.localizedDescription);
}];
// Start the request synchronously since the background service
// is already running on a background thread
[request startSynchronous];
}
#end
may be helped

iOS / Apple Watch: iPhone app network request callback blocks not triggered when app is in background

My Apple Watch app sends a message to the companion iPhone app. In the main app's handleWatchKitExtensionRequest, I send a request to the server:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
if ([[userInfo objectForKey:#"request"] isEqualToString:#"getPendingChallenge"]) {
[MyClient getPendingNotifications:someId withDomain:host withSuccessBlock:^(id responseObject) {
// process responseObject
...
reply(response);
return;
} withFailureBlock:^(NSError *error, NSString *responseString) {
// error handling
return;
}];
}
}
getPendingNotifications above is just a regular network GET request using AFNetworking.
It all works well when the app is active. Because this network request is used to populate the UI on my Apple Watch, I do not wish the main app to be active. However, when the main app on iPhone is in background, I can see the network request being sent out, but the withSuccessBlock or withFailureBlock callback blocks in the above code never gets triggered.
Can the phone app receive network request responses in background mode? If so, what am I doing wrong?
I have found a solution online that works for me, a post (http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply) by Brian Gilham.
And here's the code that works for me.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// There is a chance that the iOS app gets killed if it's in the background
// before it has a chance to reply to Apple Watch.
// The solution is to have the app respond to the request asap, then complete other tasks.
// The following code begins – and ends, after two seconds – an empty background task right at the beginning of this delegate method
// Then we kick off a background task for the real work
// For more details see http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
if ([[userInfo objectForKey:#"request"] isEqualToString:#"getPendingChallenge"]) {
[self handleWatchKitGetPendingChallengeRequest:reply];
}
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
- (void)handleWatchKitGetPendingChallengeRequest:(void (^)(NSDictionary *))reply {
...
[MyClient getPendingNotifications:someId withDomain:host withSuccessBlock:^(id responseObject) {
// process responseObject
reply(response);
return;
} withFailureBlock:^(NSError *error, NSString *responseString) {
// error handling
reply(nil);
return;
}];
}
Try to send the request as a synchronous request.
I guess that your request is asynchronous request (as it should be in regular cases). The problem that in background mode, the device will lunch your app in background thread, and you created a new thread for the request.

handleWatchKitExtensionRequest is not called

I am trying to launch the parent ios app from watchkit app. I'm using url scheme to launch the app.But it seems like
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
is nevered called. It seems like watch app does launch the app in backgound. But the parent app does not handle the watchkit request. I tried my approach in a new project and it works perfectly. Is there any thing I need to pay attention?
I've already tried to Debug>Attach to process>myapp and put a breakpoint inside handleWatchKitExtensionRequest method to confirm if it is called and it isn't called.
Here is the progress, I call openParentApplication when a button is clicked in watch app.
#IBAction func viewOniPhoneAction() {
let userInfo: [NSObject : AnyObject] = [
"userID" : user.userID
]
WKInterfaceController.openParentApplication(userInfo, reply: { (userInfo : [NSObject : AnyObject]!, error : NSError!) -> Void in
})
}
Here is my app delegeate
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSDictionary *replyDict = #{#"response": #"done"};
reply(replyDict);
}
I tried reply() in handleWatchKitExtensionRequest but I got this error in reply block from watch app
Error Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x60800026e0c0 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
I got it to work!!! Having the same issue....
Just increase the beginBackgroundTaskWithExpirationHandler time to a larger value if you still don't get the data!!! I used 2 secs previously but my network is too weak!!!
I call openParentApplication when a button is clicked in watch app:
[WKInterfaceController openParentApplication:loadDetailChatDataDictionary reply:^(NSDictionary *replyInfo, NSError *error) {
Here is my app delegate:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // increase the time to a larger value if you still don't get the data!!! I used 2 secs previously but my network is too weak!!!
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
// --------------------
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
NSString *value = userInfo[#"key"];
if ([value isEqualToString:#"loadRecentChatData"]) {
reply(#{#"recents":recents}); // Add your reply here
}
handleWatchKitRequest isn't called when you open the app via a URL scheme. It is only called in response to requests made in the WatchKit extension made using openParentApplication:reply:. That's why you aren't seeing it being executed.
You will need to wrap your reply in a background task to ensure your parent app has time to respond.
-
(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply{
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask __block = [app beginBackgroundTaskWithName:#"watchAppRequest" expirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//make your calls here to your tasks, when finished, send the reply then terminate the background task
//send reply back to watch
reply(replyInfo);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[app endBackgroundTask:bgTask];
bgTask=UIBackgroundTaskInvalid;
});
}

How to get Web service response in an iOS app when application is in Background?

I am working on an iOS application in which I am using Restful web service by NSURLConnection, when I call web service and after calling web service press home button then application goes in to background and it is not getting response in background. In my application response should get even when application will be in background state.
So please suggest me any suitable answer for this.
Should I use NSURLSession ?
NSURL *myURL = [NSURL URLWithString:#""]; // set your url here
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *getTask = [session downloadTaskWithURL:myURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// do stuff with the result
}];
// don't forget to start your task
[getTask resume];
Here's a nice tutorial you might want to take:
http://www.raywenderlich.com/51127/nsurlsession-tutorial
Use UIBackgroundTaskIdentifier, something like this
(void)applicationDidEnterBackground:(UIApplication *)application {
//Start Background Service and get data every 10 second
[self runBackgroundTask:10];
}
(void)runBackgroundTask: (int) time{
UIApplication *app;
app = [UIApplication sharedApplication];
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//create UIBackgroundTaskIdentifier and create tackground task, which starts after time
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_main_queue(), ^{
NSTimer *refreshTimer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(doRefresh) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:refreshTimer forMode:NSDefaultRunLoopMode];
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
}
doRefresh - This is method where you give your web service

iOS 4.0 Task Completion in Background

Using iOS 4.0 I am trying to do a download an image in the background when the app is suspended. What I am doing is when I get the applicationDidEnterBackground delegate call, I initiate one asynchronous NSURLConnection and set the app delegate as the delegate for the connection. But none of the NSURLConnection delegates are getting called back. I have captured the network calls using Wireshark and I can see that my request succeeded and got the response too. But since none of the delegate methods are invoked I am unable to do anything with the data.
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
// UIBackgroundTaskIdentifier bgTask is instance variable
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://animal.discovery.com/mammals/leopard/pictures/leopard-picture.jpg"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease];
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
[request release];
NSLog(#"Sent download request....");
dispatch_async(dispatch_get_main_queue(), ^{
while([application backgroundTimeRemaining] > 1.0) {
//Do something..
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
NSLog(#"Ending background task....");
});
}
What should I do to complete a download asynchronously in background when the application goes to background?
I think it's because you're ending the background task before the download actually finishes. You should put the call to [application endBackgroundTask:self->bgTask]; in the NSURLConnectionDelegate method when the download finishes (both success and unsuccessfully), and not just after calling [self.connection start]

Resources