-->

NSURLSessionUploadTask get reponse data

2019-05-21 18:32发布

问题:

I have some misunderstanding in using NSURLSession framework, that's why I decided to write small app from scratch without AFFramework/Alamofire.

I have an API that requires following steps to upload file:

  1. POST file data
  2. Get response (JSON)
  3. Post some json fields to api/save

I have a background session with such config:

let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("myBackground")
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)

I've implemented 2 methods:

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData)

where I aggregate all data

and

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?)

where I tranform this data to response object. This response object if VERY important for me.

Everything works fine, while app is in foreground, but I have problems in background.

Case 1

App crashed right after I've started to upload data. According to WWDC I need to implement

func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void)

and call this handler in didCompleteWithError method. But before calling this method I need to call api/save with data from upload response. How can I get this data?

Case 2

Mostly similar case. User stops app while upload is in progress. Than loads app in few seconds, while session works with my task. Now session calls didReceiveData, but of course, some of data is missing. What should I do in such case? How to restore response data?

回答1:

You don't mention implementing URLSessionDidFinishEventsForBackgroundURLSession (a NSURLSessionDelegate method). You really want to implement that, too. The basic process is:

  • In app delegate's handleEventsForBackgroundURLSession, you should:

    • start the background NSURLSession (which will start receiving delegate method calls associated with all of the uploads); and
    • save the completion handler (but do not call it, yet).
  • Then, in URLSessionDidFinishEventsForBackgroundURLSession (when you're done processing all of the responses), you call the completion handler you saved in handleEventsForBackgroundURLSession. (Make sure to dispatch that to the main queue.)

If you're doing all of that, when the background session is restarted, the didReceiveData calls will come in with the responses to your various uploads.

I just did a quick test, uploading five 20mb images and immediately terminating the app. Then, even though the app wasn't running, I watched the five files slowly show up on my server (obviously handled by the daemon process). When all five were done, by app was transparently restarted in the background, the handleEventsForBackgroundURLSession was called (which restarted the session), it let all of the didReceiveData calls quickly get called, and when that was done, URLSessionDidFinishEventsForBackgroundURLSession was called and my app only then called the saved completion handler.

In terms of why this isn't working for you, there's not enough to diagnose the problem. Possibilities include:

  • Maybe you terminated the app inappropriately. You can't kill the app by double tapping the home button and terminating the app there; you have to let it naturally terminate on it's own, or for diagnostic/testing purposes, I force it to terminate by calling exit(0) in code.

  • Maybe you didn't restart the session when handleEventsForBackgroundURLSession was called.

  • Maybe you called the supplied completion handler too soon (i.e. before URLSessionDidFinishEventsForBackgroundURLSession was called).

It's hard to say, but I suspect that there's something buried inside your implementation that isn't quite right and it's hard to say what it is on the basis of the information provided (assuming it isn't one of the above points). Unfortunately, debugging this background sessions is vexingly complicated because when the app terminates, it is no longer attached to the debugger, so you can't easily debug what happens after the app is restarted automatically by iOS). Personally, I either NSLog messages and just watch the device console (as well as watching what appears on the server), or I build some persistent logging mechanism into the app itself.



回答2:

For testing Background session code it is recommended to test on a real device. When writing an app that uses NSURLSession’s background session support, it’s easy to get confused by three non-obvious artifacts of the development process:

  • When you run your app from Xcode, Xcode installs the app in a new container, meaning that the path to your app changes. This can confuse NSURLSession’s background session support. Note: This problem was fixed in iOS 9; if you encounter a problem with NSURLSession not handling a container path change in iOS 9 or later, please file a bug.
  • Xcode’s debugging prevents the system from suspending your app. So, if you run your app from Xcode, or you attach to the process some time after launch, and then move your app into the background, your app will continue executing in situations where the system would otherwise have suspended it.
  • Similarly, the iOS Simulator does not accurately simulate app suspend and resume; this has worked in the past but it does not work in the iOS 8 or iOS 9 simulators.

Source: Apple Developer Forum