14

Situation: I use openParentApplication in the Watch app to call handleWatchKitExtensionRequest in the main app. This works nicely in the simulator and it also works on the actual devices (Apple Watch and iPhone) when the iPhone app is active/open.

Problem: When I run it on the actual devices (Apple Watch and iPhone), handleWatchKitExtensionRequest does not return data to openParentApplication when the main iPhone app is not active/open.

Code in InterfaceController.m in the WatchKit Extension:

NSDictionary *requst = @{ @"request" : @"getData" };
[InterfaceController openParentApplication:requst
                                     reply:^( NSDictionary *replyInfo, NSError *error ) {
                                        // do something with the returned info
                                     }];

Code in the app delegate of the main app on iPhone:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{
  if ( [[userInfo objectForKey:@"request"] isEqualToString:@"getData"] )
  {
    // get data
    // ...
    reply( data );
  }
}
John
  • 8,468
  • 5
  • 36
  • 61

1 Answers1

16

When the main app on the iPhone is not active, reply() may not be reached because the background task is killed by the OS before.

The solution is to explicitly start a background task in handleWatchKitExtensionRequest as specified in the documentation. If a background task is initiated like this, it can run up to 180 seconds. This ensures that the main app on the iPhone is not suspended before it can send its reply.

Code in the app delegate of the main app on iPhone:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{
   __block UIBackgroundTaskIdentifier watchKitHandler;
   watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
                                                               expirationHandler:^{
                                                                 watchKitHandler = UIBackgroundTaskInvalid;
                                                               }];

   if ( [[userInfo objectForKey:@"request"] isEqualToString:@"getData"] )
   {
      // get data
      // ...
      reply( data );
   }

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

In case you need to asynchroneously fetch data, use the following approach to ensure that the method does not return immediately (without calling reply):

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{ 
    __block UIBackgroundTaskIdentifier watchKitHandler;

    watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
                                                               expirationHandler:^{
                                                                   watchKitHandler = UIBackgroundTaskInvalid;
                                                               }];  

   NSMutableDictionary *response = [NSMutableDictionary dictionary];

   dispatch_semaphore_t sema = dispatch_semaphore_create(0);

   [ClassObject getDataWithBlock:^(BOOL succeeded, NSError *error){

        if (succeeded)
        {
            [response setObject:@"update succeded" forKey:@"updateKey"];
        }
        else
        {
            if (error)
            {
                [response setObject:[NSString stringWithFormat:@"update failed: %@", error.description] forKey:@"updateKey"]; 
            }
            else
            {
                [response setObject:@"update failed with no error" forKey:@"updateKey"];
            }
        }

        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];
  });
}
John
  • 8,468
  • 5
  • 36
  • 61
  • Are there any way to test it in simulator? As I don't have actual watch. @vomako – Susim Samanta May 07 '15 at 09:47
  • Q: Why its working properly in simulator though in simulator when running watch app iPhone app is in background. – Susim Samanta May 07 '15 at 09:53
  • That is a legitimate question. I experienced the problem only when I had the code run on an Apple Watch (not in a simulator). My guess would be that the simulators is not perfect. There are many differences between behaviors of simulators and the actual devices. (I just experienced a problem with synchronizing NSUserDefaults, which only occurred in a simulator and not on the actual Watch.) Having said this, the above code works in a simulator. It may, however, not be necessary to start a background task in a simulator. – John May 07 '15 at 09:56
  • Q: Where you have commented //get data I am calling asynchronous web service call there and it will work when iphone will be in background Right? – Susim Samanta May 07 '15 at 11:05
  • Unfortunately, I cannot answer this question. But I think that many developers would like to call web services. Can you try and report back? – John May 07 '15 at 11:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/77177/discussion-between-susim-and-vomako). – Susim Samanta May 07 '15 at 11:10
  • 3
    Thank you for the answer. Can you please translate this to Swift? I would do it myself but I have troubles with blocks. – Boris Y. Jun 26 '15 at 21:03
  • I tried to translate this into Swift. Please consider to update your answer. http://pastebin.com/tjn50abh – Boris Y. Jun 26 '15 at 21:16
  • 1
    @vomako the semaphore approach does not seem to unblock for some reason. how come you dont dispatch the signal in the success case? – wprater Jul 10 '15 at 23:22
  • @wprater: Thank you. You are right. I've corrected the code. – John Jul 16 '15 at 07:19
  • @vomako however, Im still having an extremely hard time getting network tasks to return reliably on the iOS device. – wprater Jul 16 '15 at 21:41
  • 1
    why do we need dispatch_after? Isnt it just a 1 second delay? – 最白目 Aug 03 '15 at 10:21