Search code examples
iosobjective-cwatchkit

How do I correctly use "openParentApplication" and "handleWatchKitExtensionRequest" so that "reply()" is called?


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

Solution

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