NSURLSession background download - resume over network failure - ios

After reading the Apple documentation about the background download with the new iOS7 api (NSURLSession), I'm a bit disappointed. I was sure that Apple was managing the pause/resume over the network availability in the background (or provide an option to do so) but no…
So reading the documentation, this is what we've got:
https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html
When any task completes, the NSURLSession object calls the
delegate’s URLSession:task:didCompleteWithError: method with either an
error object, or nil if the task completed successfully.
If the task is a resumable download task, the NSError object’s userInfo dictionary
contains a value for the NSURLSessionDownloadTaskResumeData key. Your
app should use reachability APIs to determine when to retry, and
should then call downloadTaskWithResumeData: or
downloadTaskWithResumeData:completionHandler: to create a new download
task to continue that download. Go to step 3 (creating and resuming
task objects).
So far I understand the solution, but my question is: What architecture is the best to handle the loss of the network and resume downloading in the background?
On my side I'm using reachability and each time the network is available, I resume all tasks (referenced over a NSArray when creating), and suspends them when network is lost. This works well in foreground but for the background I need help on the following points:
If my app has no connectivity in foreground, if I go to the background without connectivity all my tasks remains suspended and won't came back if network is available…
Losing network in background, stop all my downloads/tasks.
Scenario:
In foreground, I start downloading my tasks
I go to background and after 10s switch to "aireplan mode"
All my tasks got an error. So in the method URLSession:task:didCompleteWithError: I resume them using
downloadTaskWithResumeData or if I can't (because some have not
enough resume data) I'm creating a new task without resume-ing it (except if network is back at that time).
Then I put the wifi up
As I'm still in background I cannot trigger a "resume" when network is back without launching the application…
How do I address these points? Have I missed something?

As I'm still in background I cannot trigger a "resume" when network is back without launching the application…
you can use "background fetch",when the app is launched by fetch,then you can check network and resume the download task.

You should create the NSURLSession with background configurations, then your task is sent to a background demon and your app get called when it is completed.

Implementing:
application:handleEventsForBackgroundURLSession:completionHandler:
in the app delegate - without calling the completionHandler - causes the app to hang around in the background after the device loses its connection whilst suspended. That way, the app can still listen to reachability notifications and restart the download when a network connection becomes available once again. However, this is a pretty dodgy approach and may not pass Apple's app store submission guidelines. Additionally, this approach isn't much help when the connection is lost while the app is in the foreground and the connection regained whilst the app is suspended.
In the end I did the following:
Made use of the application:handleEventsForBackgroundURLSession:completionHandler: notification to pause my downloads in the background.
Made use of the intermittent background fetch notification (ie. application:performFetchWithCompletionHandler:completionHandler) to check connection status and restart any paused downloads. (hat-tip #gugupluto)
This still doesn't provide optimal download performance and may lead users to wonder why their "background download" hasn't finished once they reopen the app, but it seems to be the best we can hope for from Apple for now.

Related

Swift URLSession DataTask Fails when the app enters the background

According to Apple:
Note
You don’t have to do all background network activity with background sessions as described in this article. Apps that declare appropriate background modes can use default URL sessions and data tasks, just as if they were in the foreground.
I'm trying to use my DataTask with default session configuration and delegates (not completion handler) but my data task is always failing if I press the home button and switch back to the app again:
Task <A25361F9-CAC0-4FA8-8663-777E1C6878A2>.<2> load failed with error Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <A25361F9-CAC0-4FA8-8663-777E1C6878A2>.<2>, _kCFStreamErrorDomainKey=1, NSErrorPeerAddressKey=<CFData 0x108f07b40 [0x1db6c1420]>{length = 16, capacity = 16, bytes = 0x100201bb68118e240000000000000000}, _kCFStreamErrorCodeKey=53, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <A25361F9-CAC0-4FA8-8663-777E1C6878A2>.<2>"
What I tried: using the shared session with or without completion handler, same problem.
My question is: what does this mean "Apps that declare appropriate background modes can use default URL sessions and data tasks"? How do you declare those background modes??
The only thing I came across is UIApplication.shared.beginBackgroundTask
Is this what Apple means by "Appropriate background modes"? Or Am I missing something?
thank you
Yes, beginBackgroundTask(withName:expirationHandler:) is the correct way to request the OS to give your app a little time to finish requests it started before the user left the app. See Extending Your App's Background Execution Time.
Is this what Apple means by “Appropriate background modes”?
They’re talking about any of the techniques that might have your app running in the background. See About the Background Execution Sequence. And a “background task”, in which you’re given a finite amount of time to finish a task even though your app is no longer in the foreground, is one of those modes.
Another is background fetch. If enabled, during background fetch, the OS may, at its sole discretion, fire up your app in the background, let you perform a request, and you then call the completion handler when it’s done. (In that case, you have even less than 3 minutes, something more like 30 seconds IIRC.) That is an example of an “appropriate background mode” for which you’d use a standard/default URLSessionConfiguration, not a background one. Apple makes a point of this because it would otherwise be easy to assume that any background-related network requests would require background URLSessionConfiguration. But that’s not the case.
So, if you’re just trying to let the app keep running a bit to finish the task even if the user hit the home button, then beginBackgroundTask in conjunction with a standard URLSession is adequate. Or if you happened to be using one of those “approved background modes” (which doesn’t sound like would be the situation in your case), then again, a standard URLSession is adequate.
A background URLSession is really for those cases where request may take longer than the allotted time. Perhaps you’re downloading many very large assets, such as a movies, that might take more than a few minutes. Only in that case do you need to use a background URLSessionConfiguration.background(withIdentifier:). It just depends upon how long the request(s) will take.

NSUrlSession suspend and resume issue on device lock

I am uploading file to server by using multipart form data with NSURLSession. When the application goes in background I want to suspend the request and resume when application comes in foreground again. So I simply do [session suspend] and [session resume]. This is working fine when the app goes in background only. But if the device gets locked, when going back in foreground and try to resume, I get a network connection lost error. I understand that when device is locked, all open sockets are closed and therefore the issue, but is there some way to make this work without the need of starting the upload from beginning?
You should switch your foreground session to a background session before the app goes to background, and then there is no need to suspend it. Your file will be uploaded by OS while in background (eventually).
Unfortunately, according to the documentation, you need to use a file to perform your background upload.
From "Background Transfer Considerations":
Only upload tasks from a file are supported (uploading from data objects or a stream will fail after the program exits).
(In addition to that, there is no guarantee when or why your app will be terminated. Trying to avoid device locking alone won't be sufficient; there are a myriad other ways your app may be terminated.)

Will download resume after closing my app in Background Mode

I figured out about it is possible to download in background mode of application. I have implemented Background Fetching Mode in XCode and registered background task and its working fine.
Is it possible to resume downloading task after force closing my application by user? How?
No, you can't continue download when your app get terminated by user! Your app must require to remains in background state!!! Because if user force close app that means, he doesn't want to run it anymore. If your app is suspended by system then it can be wake up but not if it's terminated by user!
If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. This behavior applies only for normal termination of the app by the system. If the user terminates the app from the multitasking screen, the system cancels all of the session’s background transfers. In addition, the system does not automatically relaunch apps that were force quit by the user. The user must explicitly relaunch the app before transfers can begin again.
Update : (As asked in comment)
Refer the apple documentation, It states,
This method lets your app know that it is about to be terminated and purged from memory entirely. You should use this method to perform any final clean-up tasks for your app, such as freeing shared resources, saving user data, and invalidating timers. Your implementation of this method has approximately five seconds to perform any tasks and return. If the method does not return before time expires, the system may kill the process altogether.
For apps that do not support background execution or are linked
against iOS 3.x or earlier, this method is always called when the user
quits the app. For apps that support background execution, this method
is generally not called when the user quits the app because the app
simply moves to the background in that case. However, this method may
be called in situations where the app is running in the background
(not suspended) and the system needs to terminate it for some reason.
After calling this method, the app also posts a
UIApplicationWillTerminate notification to give interested objects a
chance to respond to the transition.
When any task completes, the NSURLSession object calls the delegate’s URLSession:task:didCompleteWithError: method with either an error object or nil (if the task completed successfully).
If the task failed, most apps should retry the request until either the user cancels the download or the server returns an error indicating that the request will never succeed. Your app should not retry immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.
If the download task can be resumed, the NSError object’s userInfo dictionary contains a value for the NSURLSessionDownloadTaskResumeData key. Your app should pass this value to call downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: to create a new download task that continues the existing download.
If the task cannot be resumed, your app should create a new download task and restart the transaction from the beginning.
checkout here: Life cycle of URL Session
Yes—if I understood your need right—Apple allows this with State Preservation and Restoration APIs:
Return your app to its previous state after it is terminated by the system.
Check Apple's article: Preserving Your App's UI Across Launches, for an overview of this framework.
Details about preservation process can be found in article: About the UI Preservation Process
Details about restoration process can be found here: About the UI Restoration Process
Raywenderlich have—a little outdated—tutorial implementation of this framework # State Restoration Tutorial: Getting Started

Timeout when using NSUrlSession with call started in foreground but app moved to background

If a basic call to post some data to a server using NSURLSession (in this case using Alamofire) a call is started with the app in foreground, while the call is in progress the app is move to the background and is suspended. When the app comes back to the foreground, what happens to the call that was in progress? Does it timeout (App is using default 60 seconds ephemeralSessionConfiguration) or will it receive some other error? If the timeout has not yet been reached is the call still waiting or has the OS terminated it? I have the default background mode and have no requested no background time. Trying to debug an issue that happened in the field.
Your network requests are suspended when your app is suspended. So basically the answer is that it depends on whether the server gives up on you while your app is in the background, and on whether the socket in the kernel gives up on your app while it is suspended.
As a rule, if you need to move data while your app is in the background, you should be using a background session and a download or upload task. That way, the actual data transfer happens in a separate process (that doesn't get suspended) and your app will get the data.
With that said, using a background session is fundamentally in conflict with using an ephemeral configuration, because it involves storing data on disk. So if you really need an ephemeral configuration for some reason, your app's requests are likely to just fail a few seconds after the user hits the home button, and there's not much you can do about it as long as you're making requests ephemerally.

iOS Background Execution

I am trying to understand Apple's doc for Background Execution:
Once configured, your NSURLSession object seamlessly hands off upload
and download tasks to the system at appropriate times. If tasks finish
while your app is still running (either in the foreground or the
background), the session object notifies its delegate in the usual
way. If tasks have not yet finished and the system terminates your
app, the system automatically continues managing the tasks in the
background. If the user terminates your app, the system cancels any
pending tasks.
When all of the tasks associated with a background session are
complete, the system relaunches a terminated app (assuming that the
sessionSendsLaunchEvents property was set to YES and that the user did
not force quit the app) and calls the app delegate’s
application:handleEventsForBackgroundURLSession:completionHandler:
method. (The system may also relaunch the app to handle authentication
challenges or other task-related events that require your app’s
attention.) In your implementation of that delegate method, use the
provided identifier to create a new NSURLSessionConfiguration and
NSURLSession object with the same configuration as before. The system
reconnects your new session object to the previous tasks and reports
their status to the session object’s delegate.
If I use NSURLSession, so when app goes background when uploading process is still on going, the process won't be killed or died as long as the application isn't terminated by user (I assume this is by killing my app from app list) ?
Read the text carefully. As all good documentation, it says very clearly what it means, and you just need to read it carefully.
You didn't read it carefully.
There are three cases: Your app is still running when a task finishes, your app has been shut down by the system when the last task finishes, or the user has closed down the app before the last task finishes. No, it doesn't say anywhere that the app is kept alive. And the documentation says clearly what happens in each case.
iOS kills apps that are in the background and makes it look to the user as if they are still running.

Resources