Async download inside didReceiveRemoteNotification
Asked Answered
M

3

10

My app is configured to support silent push (content-available), and also supports background fetch. Want I need to achieve is upon receiving a silent push, I need to send an ajax request to the server, get back the data and save it (persist it with CoreData).

Of course all this happens without the user ever opening the app. When he do opens up the app, a fresh data will be waiting. This is the silent push call back:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {

    // Use Alamofire to make an ajax call:
    //...
    let mutableURLRequest = NSMutableURLRequest(URL: URL)
    let requestBodyData : NSMutableData = NSMutableData()

    mutableURLRequest.HTTPBody = body
    mutableURLRequest.HTTPMethod = "POST"
    mutableURLRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
    mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

    request(mutableURLRequest)
        .responseJSON { (req, res, data, error) in

            //We do not reach this code block !

            // Save the incoming data to CoreData
            completionHandler(UIBackgroundFetchResult.NewData)
    }
}

Now, the problem is that when a notification arrives and the delegate is called, the ajax code executes, make the call to the server, and then exits. The code inside the ajax callback will not run. But, when I open the app and brings it to the foreground, suddenly this code section wakes up and continues to run.

This is not the desirable behaviour because when I open the app I still need to wait 1-2 seconds for the those operations to run (updating the UI etc)

What am I doing wrong here? Should I open a new background thread for this operation?

UPDATE:
I moved completionHandler(UIBackgroundFetchResult.NewData) into the ajax callback, but still this dose not fix the original problem, which is that this ajax callback code block won't execute.

UPDATE 2:
It seems like it's an issue with Alamofire, and that I will have to use NSURLSession to make this ajax call. Trying to put some code together.

Maxima answered 16/8, 2015 at 2:37 Comment(1)
Hey, did you solve your problem?Staffordshire
M
6

You're not using the 'completionHandler' in a proper way

Calling this completion handler is like telling the iOS you're done with your task and it can now put your app back to sleep or to the background. You're calling it immediately

So you just need to change your code to something like this

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    // Use Alamofire to make an ajax call:

    let mutableURLRequest = NSMutableURLRequest(URL: URL)
    let requestBodyData : NSMutableData = NSMutableData()

    mutableURLRequest.HTTPBody = body
    mutableURLRequest.HTTPMethod = "POST"
    mutableURLRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
    mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

    request(mutableURLRequest)
        .responseJSON { (req, res, data, error) in
            // if there was an error then {
            //     completionHandler(UIBackgroundFetchResult.Failed)
            //     return
            // }

            // Save the incoming data to CoreData
            completionHandler(UIBackgroundFetchResult.NewData)
    }
}

Here's the Apple Documentation description for that completionHandler

The block to execute when the download operation is complete. When calling this block, pass in the fetch result value that best describes the results of your download operation. You must call this handler and should do so as soon as possible. For a list of possible values, see the UIBackgroundFetchResult type.

And also this answer could be helpful

Matriculation answered 16/8, 2015 at 3:18 Comment(5)
That's not what the OP is describing. The method gets past the completion handler, executes to the bottom and exits -- the request is going out. It's simply that you can't queue up additional background work on a suspended thread and expect it to execute. It wouldn't make sense for a method to get suspended in the middle of execution unless it exceeds the documented 30-second timeout.Kassia
@Kassia This answer still makes a valid point. An app should not simply call the completion handler as shown in the question's code. It should be used correctly.Tryparsamide
@Kassia it's not just executed at the bottom, it's executed after the callback is made and the data has been downloaded, don't understand your downvoteMatriculation
Because it makes the factually incorrect claim "you just need to do xyz..."Kassia
I fixed the changed the completionHandler position, but still this dose not solve the original problem.Maxima
T
3

To add to this answer. When I do this, I have gotten spotty support when starting that background fetch after didReceiveRemoteNotification. I can start an Alamofire connection, but it may not work, it will die almost immediately.

Scouring the internet and looking at silent notification tutorials, no one seems to mention the use of:

  • beginBackgroundTaskWithExpirationHandler:
  • beginBackgroundTaskWithName(_:expirationHandler:)
  • endBackgroundTask(_:)

Everyone says:

ya, you get about 30 seconds per the docs to get your work done or you need to use the NSURLSession Background Session

It appears in my, own testing and hair pulling, that in order to buy yourself that 30 seconds you likely need to invoke the beginBackgroundTask* + endBackgroundTask methods on the UIApplicationDelegate

References:

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/#//apple_ref/occ/instm/UIApplication/beginBackgroundTaskWithName:expirationHandler:

https://forums.developer.apple.com/message/96731#96731

Specifically, the apple dev forum answer is done by a member of Apple Developer Relations, Developer Technical Support, Core OS/Hardware

He specifically says:

Huh? You’re issuing the request in the NSURLSession shared session. Such requests will only run as long as the app remains running. If the app is suspended again, which is typically what happens in this case, the request will stop halfway through. You have two options here:

  • Continue using the shared session and use a UIApplication background task to prevent your app from suspended. IMPORTANT For this to work the request must completely quickly, because the UIApplication background task will typically only give you about 30 seconds of execution time.
  • Run the request in an NSURLSession background session, which will organise to resume your app when the request is complete.

Note the first option Continue using the shared session and use a UIApplication background task to prevent your app from suspended.

Trifid answered 28/1, 2016 at 22:57 Comment(0)
K
1

REWRITE

You're not using AlamoFire properly, but you can configure it to do fetches when in a background state:

AlamoFire Download in Background Session

And the completion handler should get called at the end of the completion block of the AF call, not at the beginning of the method.

Kassia answered 16/8, 2015 at 3:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.