Search code examples
iosobjective-cafnetworkingwatchkitapple-watch

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?


Solution

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