Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous code doesn't execute until app foregrounded in application:didReceiveRemoteNotification:fetchCompletionHandler:

In our app, we want to download a small amount of data in response to a push notification. So far, push notifications are working smoothly, launching the app in the background and causing didReceiveRemoteNotification to be called.

The problem is that, after this method returns, the app doesn't get any more CPU time until it's foregrounded again, so there's no opportunity to fetch that data asynchronously in the background.

Reducing this to the simplest case, I'm still unable to get asynchronous code running.

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    [application setApplicationIconBadgeNumber:1];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [application setApplicationIconBadgeNumber:9];

        completionHandler(UIBackgroundFetchResultNewData);
    });
}

In response to a push, the app launches in the background and the badge count is set to 1, but the badge number is not set to 9 until the app is launched from the home screen.

Shouldn't iOS keep running the app until that completion handler is called, up to 30 seconds?

The Info.plist has the remote-notification background mode specified, the push payload contains 'content-available' : '1', and I'm not quitting the app by swiping up in the app switcher.

To add, we're using Parse to send this push notification using the following Javascript:

Parse.Push.send({
    where: installationQuery,
    data: {
        "content-available": 1,
    }
}, { success: function() {},
    error: function(error) {}
});
like image 739
Jeff Smith Avatar asked Aug 16 '15 03:08

Jeff Smith


1 Answers

First take a here and make sure you enabled push notification and added the content-available field:

Using Push Notifications to Initiate a Download If your server sends push notifications to a user’s device when new content is available for your app, you can ask the system to run your app in the background so that it can begin downloading the new content right away. The intent of this background mode is to minimize the amount of time that elapses between when a user sees a push notification and when your app is able to able to display the associated content. Apps are typically woken up at roughly the same time that the user sees the notification but that still gives you more time than you might have otherwise. To support this background mode, enable the Remote notifications option from the Background modes section of the Capabilities tab in your Xcode project. (You can also enable this support by including the UIBackgroundModes key with the remote-notification value in your app’s Info.plist file.) For a push notification to trigger a download operation, the notification’s payload must include the content-available key with its value set to 1. When that key is present, the system wakes the app in the background (or launches it into the background) and calls the app delegate’s application:didReceiveRemoteNotification:fetchCompletionHandler: method. Your implementation of that method should download the relevant content and integrate it into your app. When downloading any content, it is recommended that you use the NSURLSession class to initiate and manage your downloads. For information about how to use this class to manage upload and download tasks, see URL Loading System Programming Guide.

Next, is there a reason your using "dispatch_after" with 2 seconds delay?

It is possible that since you call "dispacth_after" by the end of the run loop iOS "thinks" there's no pending work to do and puts the process to sleep so by the time the block is dispatched no one is listening to it.

Replacing it with "dispatch_async" might solve this.

Finally, if you do need to delay, you should tell iOS you need some time in the background, like this -

    UIApplication *application = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
    if (backgroundTaskId != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:backgroundTaskId];
        backgroundTaskId = UIBackgroundTaskInvalid;
    }
}];

Then do your background work. Don't forget end the task when your work is done. call something like -

if (backgroundTaskId != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:backgroundTaskId];
        backgroundTaskId = UIBackgroundTaskInvalid;
    }
like image 156
Yoshkebab Avatar answered Oct 21 '22 02:10

Yoshkebab