GCD sendSynchronousRequest applicationDidEnterBackground - ios

Every time my app loads it calls a web service to check if an update is available to download for the app. If there is it downloads it.
I'm using Grand Central Dispatch to synchronously download the updates in a different thread so I can show a loading icon on my main thread. I need to synchronously download because the app needs the updates before it continues.
However the problem occurs if the download takes a while and the screen goes to sleep, effectively calling applicationDidEnterBackground. When the user turns the screen back on and the app resumes I get the following error:
EXE_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)
What is the best way around this? Trying to pause the download and resume again when the app resumes or to create a background task and continue downloading? I'm not sure which is the best method.

I think the safest answer would be that, unless your app otherwise has background privileges (audio, gps, external device, etc), yes, you should pause/stop any network downloads when -applicationDidEnterBackground is called and resume when your app comes back.

Related

Handling large number of API requests in iOS App (Not using Alamofire)

How to handle the states in which the app goes into foreground setting off a number of requests (around 3-4 minimum) because that information is required in the app, and then going to background?
What I have tried is to use a RequestManager to suspend the URLSessionDataTasks when app goes into background and when app resumes, resume those tasks again. But I don't see this working very well.
Is there a standard way to go about this?
Suspending tasks won't work, because the session no longer exists if your app gets jettisoned for low memory.
The most straightforward approach would be to use a download task in a background session, then read the resulting temporary file when it finishes downloading. Download and upload tasks in background sessions are the only types of tasks that can survive your app getting jettisoned while in the background because of memory pressure.
If you absolutely must avoid downloading while the app is in the background (why?), you could create a download task in either a foreground or background session, then stop the download tasks by calling cancelByProducingResumeData: when your app gets backgrounded. You can later continue the request by calling downloadTaskWithResumeData:.
There is a rather large caveat with that approach, though, which is that the resume data portions of the API are not nearly as well tested as the background downloading portions. Case in point: in every version of iOS 10 from the first beta until 10.2, support for resume data was completely broken. (There is a rather horrific workaround, in case you choose to go down that path.)
So I would recommend the first approach unless you have some contractual or legal obligation not to do so.

Is it possible to use AVAssetExportSession's `exportAsynchronouslyWithCompletionHandler:` method when the app is in the background?

I'm working on an iOS app, uploading videos from the Camera Roll, using NSURLSession with a background configuration. The user can queue up multiple videos for upload (the queue is executed serially).
A single upload consists of:
Getting an AVURLAsset reference to the PHAsset using PHImageManager's requestAVAssetForVideo method.
Exporting the resource to a temp directory (because you cannot upload straight from the AVURLAsset's URL).
Uploading the resource using an NSURLSessionUploadTask
I can queue up multiple videos and the process works well in the foreground. They complete one after another.
But if I queue up several videos and then background the app. As soon as execution reaches the exportAsynchronouslyWithCompletionHandler: stage it stalls until I foreground the app again. (I know this because I'm posting debug statements in local notifications, visible on the lock screen).
Is it possible to use exportAsynchronouslyWithCompletionHandler: when the app is backgrounded?
Edit 1
I've tested this while connected to the debugger and while not, the app never executes the copy command. But does so only when the app is foregrounded again.
Edit 2
I posted a similar question about whether using NSFileManager's copyItemAtURL:toURL:error: is a viable alternative (but I'm seeing the same behavior so don't think it is).
In general, if you need just a little time (up to a few minutes) to finish up some tasks even after the user leaves the app, you just request this from the OS. See the Executing Finite Length Tasks section in the Background Execution Chapter. So, begin the background task when you call exportAsynchronouslyWithCompletionHandler, and end it in the completion handler for that method.
If you are also using a background NSURLSession. In that case, if the app is not in foreground when the tasks finish, it calls the app delegate's handleEventsForBackgroundURLSession method, which passes a completionHandler block. One must:
Save the completionHandler provided to handleEventsForBackgroundURLSession;
Instantiate the NSURLSession with the same background identifier as the original background session;
Let the session call the appropriate delegate methods for the completion of the tasks; and
The session will call URLSessionDidFinishEventsForBackgroundURLSession when they're all done, at which point you'd generally call the completionHandler we originally received in the app delegate.
In your case, you will want to defer the call to the saved completionHandler until after all of the asynchronous exportAsynchronouslyWithCompletionHandler handlers are done, too. There are bunch of ways you could do that (e.g. dispatch groups, etc.), but hopefully that illustrates the moving parts involved in this process.

Saving in Background Thread not getting finished

I have an instance of AVAudioRecorder and I have noticed that when the user closes the app too fast with a larger recording the file does not get saved properly.
This even happens when I call [recorderObject stop] in the main thread and the file gets saved locally.
I have also tried moving the file after the recording has stopped (in the (void)audioRecorderDidFinishRecording:successfully: method). But I have noticed that when I do the move with NSFileManager in a background thread with high priority, it too doesn't always finish.
Is there a way for me to insure that the files get saved, even if the user exits the app shortly after finishing a longer recording?
Thanks
Review Apple's documentation for executing tasks in the background.
Apps moving to the background are expected to put themselves into a
quiescent state as quickly as possible so that they can be suspended
by the system. If your app is in the middle of a task and needs a
little extra time to complete that task, it can call the
beginBackgroundTaskWithName:expirationHandler: or
beginBackgroundTaskWithExpirationHandler: method of the UIApplication
object to request some additional execution time. Calling either of
these methods delays the suspension of your app temporarily, giving it
a little extra time to finish its work. Upon completion of that work,
your app must call the endBackgroundTask: method to let the system
know that it is finished and can be suspended.

NSURLSession resume from crash

Has anyone got any tips on how to reconnect to a download in a NSURLSession after a crash.
If my app crashes, I believe the download continues on the device anyway, and then when my app relaunches I can use the same sessionID in:
NSURLSessionConfiguration.backgroundSessionConfiguration(sessionID)
However, am I then supposed to call 'session.getTasksWithCompletionHandler' to see if there are any tasks? The docs aren't clear.
My UI has a download progress bar for each file download, so ideally when I relaunch the app, it would try and reconnect and hook the progress back up to the UI.
At the moment what is happening is I restart the download, and my progress bar flickers because there are 2 downloads in progress - the old one and a new one, and then it gets itself into a state...
Ok here is what I found, in the URL System Programming Guide:
In both iOS and OS X, when the user relaunches your app, your app
should immediately create background configuration objects with the
same identifiers as any sessions that had outstanding tasks when your
app was last running, then create a session for each of those
configuration objects. These new sessions are similarly automatically
reassociated with ongoing background activity.
So once I create the session again and hook up the delegate, the delegate methods start firing again, and I use getTasksWithCompletionHandler to see if there were any tasks in progress and return this to my UI and the rest of the system

NSURLSession background download - resume over network failure

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.

Resources