3

I've built an app around AFNetworking 2.0's AFHTTPSessionManager and its nice HTTP convenience methods. I now need to ensure that all of this networking functionality can run in the background, and I'm rather concerned.

Reading Apple's documentation, I can see that data tasks are not supported for background sessions. After looking briefly at AFHTTPSessionManager's implementation of GET, POST, PUT et al, it seems to use NSURLSessionDataTask across the board.

Is there something I'm missing, or do I have some redesign and rework to do?

In the event that I'm right (and I suspect I am), and that this codepath won't allow me to background uploads and downloads, is there any reason I couldn't wrap AFURLSessionManager's other methods that use non-data tasks in the same way the current HTTP methods wrap "dataTaskWithRequest:completionHandler"? For example for a POST I could perhaps use "uploadTaskWithRequest:fromData:progress:completionHandler"?

I'm asking, since I'm wondering if this is a viable route, why the AFNetworking devs didn't use it, so that AFHTTPSessionManager's convenience methods would allow for background transfers.

Darren Black
  • 1,030
  • 1
  • 9
  • 28

1 Answers1

0

AFNetworking allows you to perform background requests (though be careful to not use any of the task-specific completion blocks and make sure you implement the appropriate app delegate methods; see AFNetworking 2.0 and background transfers). You probably also can use requestWithMethod of AFHTTPRequestSerializer to simplify the process of building the request, too (though, IIRC, you cannot use HTTPBody of the request with background upload requests, so you might have to save the body of the request to a file and then issue your background upload request using that file).

But you're absolutely correct that you cannot use the AFHTTPSessionManager methods to initiate NSURLSessionDataTask tasks when using background session. Regarding why they implemented it that way, that's a question better suited for their issues forum.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi Rob. I've read that post several times, and I was hoping you'd answer this :) This might be a little off topic from the OP, but I'm curious about the background session ID. As it stands, I've got numerous operations and types of operations in my AFHTTPSessionManager subclass. At any point, there could be any combination of these ops in progress, and what needs to be done on completion of these ops varies - sometimes updating a CoreData model, sometimes nothing. Given that I only have 1 session ID per manager, I suppose I need to create one manager per op for this kind of control? – Darren Black Feb 12 '15 at 14:40
  • 1
    Either that or you have to maintain some cross reference (and make sure it's maintained in persistent storage) between the requests and the type of action you want to perform in the completion delegate methods. Or perhaps you can infer the correct action on the basis of the task's `originalRequest.URL`. There are a bunch of ways of tackling this... – Rob Feb 12 '15 at 14:44
  • Yeah... the thing is this is somewhat complex. The app currently relies heavily on the success and failure blocks of AFHTTPSessionManager, with several layers of functionality mirroring it. So it's not just a type of action that's needed on completion, it's an action specified by the calling layer. So what I'm looking to do here is create an interface that mimics what AFHTTPSessionManager provides, but doesn't have the limitation of not working in the background. Is this even doable? I'm effectively looking at blocks within blocks within blocks on completion of a task... – Darren Black Feb 12 '15 at 15:46
  • You either need to come up with a design that only uses completion blocks at the session level, or you need to tackle background requests differently (e.g. rather than background `NSURLSessionConfiguration`, use standard session, but rest a few minutes to finish requests with `beginBackgroundTask`, something that only works if the requests don't take more than a few minutes to finish). – Rob Feb 12 '15 at 17:01
  • Sure. I guess what baffles me is that there can even be a completion block after the application is terminated and revived. If this block performs an operation on another object (for instance the caller), I'm confused how that can possibly work when the app is revived... I guess I'll need to run some tests, unless you know this? I'm assuming that the task always comes back intact (with the complete request + response etc) though. That part is somewhat easier for me to swallow. – Darren Black Feb 12 '15 at 18:49
  • 1
    No, your intuition here is absolutely correct. I must have been unclear in that other post. _The blocks do not survive._ That's why you cannot rely on any task-specific blocks because they're all lost. So, what I did is I reinstantiated the session-level blocks the next time the app runs, thereby limiting me to some simple-minded handler that acts solely on the basis of the task (e.g. grab the URL and make decisions from that). But you're right that all of the old blocks and variables and stuff are all long gone. – Rob Feb 12 '15 at 19:10
  • Alrighty... there's something that remains unclear to me here. According to Apple's documentation, the only method I can expect to be called when the app has been terminated while performing a background download is handleEventsForBackgroundURLSession, and this is called when all tasks for a session have been completed. Does AFNetworking contain some magic which will call the block you've set in setDownloadTaskDidFinishDownloadingBlock? Or do I lose per-task granularity totally when the app is terminated by the system? – Darren Black Feb 16 '15 at 10:42
  • Or is handleEventsForBackgroundURLSession perhaps a call which tells you to set up your sessions to allow you to handle some task-specific callback(s) which are imminent? This seems to me the only viable route actually, since a catch-all of "this session is finished doing... something" with no details to follow would be pretty much useless. – Darren Black Feb 16 '15 at 10:53
  • 1
    When the session is done, your app is awaken and `handleEventsForBackgroundURLSession` is called in which we reinstantiate the background session and save the `completionHandler`. Now `NSURLSession` will call the session's task completion delegate methods (in rapid succession, if you had multiple background tasks that finished), meaning that `downloadTaskDidFinishDownloadingBlock` that we just recreated will be called for each background task. Finally, `URLSessionDidFinishEventsForBackgroundURLSession` is called, and the block that we defined to call the saved `completionHandler` is called. – Rob Feb 16 '15 at 13:05
  • 1
    "Or is `handleEventsForBackgroundURLSession` perhaps a call which tells you to set up your sessions to allow you to handle some task-specific callback(s) which are imminent?" So, yes, that's precisely what happens: Our process of calling our singleton to save the `completionHandler` is, more importantly, instantiating the session, which in turn, allows the background task completion methods to be automatically called. – Rob Feb 16 '15 at 13:12
  • Looking at the available session-level completion handlers, it seems that the setDownloadTaskDidFinishDownloadingBlock method doesn't allow for errors. So if the download fails, you can't react? In this situation, would it be necessary to use the setTaskDidCompleteBlock to react to download errors? – Darren Black Feb 24 '15 at 13:22
  • 1
    Yes. And remember, there are two types of errors. There's basic connectivity issues which are handled by `setTaskDidCompleteBlock`. But errors like "404 - not found" are not reported that way. You also have to check the `statusCode` in `setDownloadTaskDidFinishDownloadingBlock` (e.g. check to see if `[downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]`, and if so, see whether `[(NSHTTPURLResponse *)downloadTask.response statusCode]` is equal to 200 or not. – Rob Feb 24 '15 at 14:19
  • Thanks... you've been a spectacular help thus far. I'm getting some odd behaviour when handling wakes from termination (I simulate this by killing the app from Xcode after some transfers are initiated)... and I'm wondering about the significance of the completion handler provided in setDidFinishEventsForBackgroundURLSessionBlock:. Examining the source, it seems AFN calls this as soon as it gets the appropriate callback from the system. But what semantics does this have? Does it signal that it's ok for the system to kill the app again? – Darren Black Feb 25 '15 at 13:33
  • ... I'm using NSNotifications to trigger handling of finished transfers / tasks elsewhere in the system, since I don't want to couple the 'business logic' of the app directly to the lower level network operations. – Darren Black Feb 25 '15 at 13:35
  • 1
    The documentation says, "Calling this completion handler lets the system know that your app’s user interface is updated and a new snapshot can be taken." Personally, I think there's something far deeper involved here, that calling this completionHandler is your way of informing the OS that the processing of all of these responses is complete and that the app can therefore safely stop background execution. In terms of your "odd behavior", I'd suggest you post your own question illustrating precisely what's happening. But you should really stop posting follow up questions here in comments here. – Rob Feb 25 '15 at 13:57