using begin​Background​Task​With​Expiration​Handler​ for upload - ios

From the doc it looks like uploading a file is a good use case for using begin​Background​Task​With​Expiration​Handler​. I've found that using
let uploadTask = session.uploadTask(with: request as URLRequest, fromFile: file)
uploadTask.resume()
will already run while the app is backgrounded (I'm getting upload progress pings for a while). Additionally I can set the URLSession to be backgrounded:
let config = URLSessionConfiguration.background(withIdentifier: "uploads")
session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
So what is the advantage of using begin​Background​Task​With​Expiration​Handler ? Will it extend the time I've to finish my upload? If so can I know by how much (didn't see anything about this in the doc)? Or is it just that I'll get pinged before the app stops? Should I use it in combination with a background URLSession?
Additionally the docs says that the handler will be called shortly before the app’s remaining background time reaches 0 Does it mean that the app will be terminated after that? ie can I assume that the next call will be application:didFinishLaunchingWithOptions or can it be applicationDidBecomeActive ?

So what is the advantage of using begin​Background​Task​With​Expiration​Handler ?
If you are going to use URLSessionConfiguration.background, there is no such advantage and you should not use beginBackgroundTask(expirationHandler:) at all. Your entire premise (your very first sentence) was wrong. Uploading a file is not a good use case for beginBackgroundTask(expirationHandler:). It's a good use case for URLSessionConfiguration.background. The two things have nothing to do with each other.

This background task will let your app continue to run in background after the user leaves your app for an extra 3 minutes or so (check background​Time​Remaining for actual value) to let your request finish. And, yes, near the end of that 3 minutes, the timeout handler will be called if you haven't yet ended the background task.
So, if you end the background task during the normal flow of your app, this timeout closure won't need to be called. This closure is solely for any quick, last minute cleanup, that you might need to do before your app stops running in the background because it timed out before you had a chance to indicate that the background task ended. It's not for starting anything new, but just any last second clean-up. And make sure to end the background task in this timeout handler ... if you don't end the background task, the OS will summarily kill your app rather than just suspending it. Often, the only thing you need to do in this timeout closure is end the background task, but if you need to do any other cleanup, this is where you can do it.
Needless to say, you must end your background task (either when the network request finishes, or in the timeout handler if your app didn't yet get a chance to end the background task in its normal flow). If you don't, your app won't just be suspended, but rather it will be killed.
Regarding making assumptions about what happens when your app is restarted by the user later, you can't make any assumes about which app delegate method will be called. Even if you gracefully ended the background task, you have no assurances that it won't get jettisoned for other reasons (e.g. memory pressure). So don't assume anything.

Related

Handling of Alamofire requests as iOS app is terminating

AppDelegate.applicationWillTerminate is called when the application is about to terminate. In this function, I am issuing a network request via Alamofire, to notify the server that the app is terminating. Alamofire's response handler is never invoked. It looks to me like the termination completes before the completion handler is invoked.
Alamofire's completion handlers appear to run on the main thread. I found documentation saying that the app is responsible for draining the main queue: "Although you do not need to create the main dispatch queue, you do need to make sure your application drains it appropriately. For more information on how this queue is managed, see Performing Tasks on the Main Thread." (From https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html) And this is where I am stuck.
How do I drain the main thread? I need to ensure that this last Alamofire request runs before the main thread exits.
Don't worry about “draining” the main thread. The problem is more simple than that. It's just a question of how to do something when your app is leaves the “foreground”/“active” state.
When a user leaves your app to go do something else, it is generally not terminated. It enters a “suspended” state where it remains in memory but does not execute any code. So when the app is suspended, it cannot process your request (but the app isn't yet terminated, either).
There are two approaches to solve this problem.
You could just request a little time to finish your request (see Extending Your App's Background Execution Time). By doing this, your app is not suspended, but temporarily enters a "background" state, where execution can continue for a short period of time.
The advantage of this approach is that it is fairly simple process. Just get background task id before starting the request and you tell it that the background task is done in the Alamofire completion handler.
The disadvantage of this approach is that you only have 30 seconds (previously 3 minutes) for the request to be processed. If you have a good connection, this is generally adequate. But if you don't have a good network connection in that period, the request might never get sent.
The second approach is a little more complicated: You could make your request using a background URLSession. In this scenario, you are effectively telling iOS to take over the handling of this request, and the OS will continue to do so, even if your app is suspends (or later terminated during its natural lifecycle).
But this is much more complicated than the first approach I outlined, and you lose much of the ease and elegance of Alamofire in the process. You can contort yourself to do it (see https://stackoverflow.com/a/26542755/1271826 for an example), but it is far from the obvious and intuitive interface that you're used to with Alamofire. For example, you cannot use the simple response/responseJSON completion handlers. You can only download/upload tasks (no data tasks). You have to write code to handle the OS restarting your app to tell you that the network request was sent (even if you're not doing anything meaningful with this response). Etc.
But the advantage of this more complicated approach is that it is more robust. There's no 3 minute limit to this process. The OS will still take care of sending the request on your behalf whenever connectivity is reestablished. Your app may may even be terminated by that point in time, and the OS will still send the request on your behalf.
Note, neither of these approaches can handle a "force-quit" (e.g. the user double taps on the home button and swipes up to terminate the app). It just handles the normal graceful leaving of the app to go do something else.

Chaining Background Tasks Alamofire

I have a question about Alamofire and its behavior with a SessionManager configured for background Tasks. I am using it to upload a video in the background.
Step I: Uploading Video:
This part is standard, however, when the upload completes:
Step II: Completing Upload:
I need to send a DELETE request to the server letting it know that the video upload is complete. If successful, the response will contain a location header for the newly uploaded video.
Step III Add Video MetaData:
With this location I need to PATCH request the video metadata: Title and Description.
So my question is about overriding the Session Manager delegate closures. I can override sessionDidFinishEventsForBackgroundURLSession and taskDidComplete and when I am completely finished with the background I need to call the system completionHandler that I am storing as a property on the SessionManager... So when/ where should I fire off the DELETE request and then when/where should I fire off the PATCH request?
Should I create 3 different background session configuration identifiers so I can identify them and make sure I chain them in right order? Because obviously I cannot say in the closure: the first time you are called fire off this request, but the second fire off this one? And which closure of the 2 is the right one to finish off the whole process and call the system completionHandler? I'm not sure if this is right because I will be out of sync with the Alamofire upload response handler.
Also I am wondering about the Alamofire response handlers. If the app was in the foreground the whole time? I would simply chain the alamofire requests together using the response handlers? but if the app terminated and is running in the background will these handlers still be around?
Any insight here would be greatly appreciated. I realize there is a lot going on here and Apple eve rate limits background tasks, I'm just wondering if this is possible and if so how to go about it?
Should I create 3 different background session configuration
identifiers so I can identify them and make sure I chain them in right
order?
I dont think you will need multiple background sessions just to identify for which request the completion block was called and to chain the next request. You can achieve it with
Asynchronous NSOperation :
You can make use of Asynchronous NSOperations to chain the multiple requests. Add the dependency among the operations and let the iOS handle scheduling and handling the dependencies. Please note : I mentioned Asynchronous NSOperation. NSOperations are Synchronous by nature.
Promise kit :
If Asynchronous NSOperations are way too complicated, you can always use Promise kit. Promise kit will take care of executing the request only after the specific request completes and the whole dependency chain will short circuit if one on the top fails.
Simply create a new data task in the completion block of upload task to upload the video. Rather than using the delegate pattern for tasks use completion blocks. That way you dont have to identify for which request the delegate was called, as each task will have its own completion block, you can easily chain them up while writing the code.
if the app terminated and is running in the background will these
handlers still be around?
Am not 100% sure though, but as far as I know, when you schedule the background task (background session), task will continue to execute even if the app is killed.After all, thats why we use background session. So I believe the completion handlers will be executed even if you kill the app.

GCD background task maximum TTL

I need to be able to program background tasks. Little "crons" if you will, that execute some simple code. While not being an expert in GCD I was wondering:
What is the maximum time I expect for the background task to actually perform its duties in the background before apps quits completely
Can I "program" multiple tasks and expect them to complete in timely order
Are they only active as long as the app is launched? ( I bet they are, unlike local notifications that dont really care whether the app is running in the background or not, so just asking to be sure)
How to I keep track of them and cancel if needed?
For instance I able to do something like this and task is performed. I went as far as 1 minute here and it works.
let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 60) {
// Some action here
UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
})
You can print the time remaining for a given session using backgroundTimeRemaining (docs here). Apple makes no guarantees about what this time will be, it varies with battery level, hardware, resources, etc., so probably no good for a long running persistent background task. You might want to consider the background fetch API, although this is similarly throttled by iOS and you don't have complete control over when it runs.

NSURLSessionDownloadTask cancelByProducingResumeData vs suspend

I need to pause download tasks and resume it even after app restarted. But I am unsure which method should I use, suspend or cancelByProducingResumeData.
With cancelByProducingResumeData I can get the partially downloaded data and recreate download task with it. However I have to manually manage the data, save it to file, read it back, and recreate the task and ensure the new task doesn't fail.
With suspend, I can pause and resume the download task. But can I resume this task after the app is restarted? I am using background session so tasks are preserved across restart.
cancelByProducingResumeData have requirements for it to work, does those requirements also applies to suspend/resume? Or suspend/resume is only mean for "temporarily suspends a task" as the document said?
You're overthinking the problem. The "resume data" for a download task is not the data that the task has received up to that point. It is a tiny blob of bookkeeping data—the sort of thing that you'd typically throw into an array in NSUserDefaults.
With that said, to answer the original question, a task is only valid within the context of a session. So for a foreground session, once your app quits, the session ceases to exist, so it is no longer possible to gain access to tasks in that session. Therefore, it is not possible to resume a suspended task after you relaunch the app because the task no longer exists (because its session no longer exists).
For a background session, you'd pretty much have to ask somebody on the Foundation Networking team to get an answer to that one, because it depends on the extent to which you can recreate a session after the fact. However, my guess is that it probably won't work there, either, and if it does, you should consider it unsupported.
After some research on apple developer forms, I found this
Tasks suspension is rarely used and, when it is, it's mostly used to temporarily disable callbacks as part of some sort of concurrency control system. That's because a suspended task can still be active on the wire; all that the suspend does is prevent it making progress internally, issuing callbacks, and so on.
OTOH, if you're implementing a long-term pause (for example, the user wants to pause a download), you'd be better off calling -cancelByProducingResumeData:.
So suspend may not actually stop downloading and I should use cancelByProducingResumeData: for long-term pause.

On iOS, can you begin work on the background thread before applicationDidEnterBackground is called?

I'm updating some content in my app and I want that to finish up when the user switches out of the app. It seems like I have to stop my currently-running update and start another one in the applicationDidEnterBackground method. It would be much more convenient if I could mark some work as something I want to run in the background before that method is called.
Here's the scenario:
I'm trying to update content and start running a SQL update that takes a bit of time. (More than the five seconds you have to return from applicationDidEnterBackground.)
The user leaves the app. The current update is suspended, but I really want it to finish.
I can start a new update which picks up where the other left off, but if the user switches back into the app I have SQL-level concurrency issues.
Is the only option to break down the SQL queries to smaller batches so I can switch over cleanly in the applicationDidEnterBackground callback? It almost doubles the execution time. (I'm not worried about the OS killing my background task, resume is handled fine.)
Ideally I'd be able to have the existing work continue seamlessly in the background (at the pleasure of the OS), is that possible? Are there better options?
(I've read the Programming Guide's section on executing background tasks.)
You can continue to run your current threads. You don't have to stop any of them and start new one.
The only thing which you need to do, if to use beginBackgroundTaskWithExpirationHandler (as proxi mentioned) when you entering background and use endBackgroundTask when you are done. This method gives your application up to 10 minutes of execution. UI of your application won't be accessible (since a user switched to another app), but all threads of your app will continue to run. System will pause all threads when your will do endBackgroundTask or 10 minutes will expire.
I would organize it like this
Have you processing threads running
In applicationDidEnterBackground call beginBackgroundTaskWithExpirationHandler.
Save UIBackgroundTaskIdentifier somewhere accessbile.
At the end of your processing thread, check whether UIBackgroundTaskIdentifier isn't 0 and if it's not, call endBackgroundTask. Set UIBackgroundTaskIdentifier to zero.
If I understand right, you just have to wrap your long-running operation into beginBackgroundTaskWithExpirationHandler block. See the method's documentation for details on how to use it.

Resources