How can I know which tasks are running in the background? - ios

I'm trying to implement a seemingly common feature in my app: to upload files in the background. I run such tasks by calling -[UIApplication beginBackgroundTaskWithExpirationHandler:], so those tasks are each identified by a UIBackgroundTaskIdentifier.
Please take a look at the diagram below. Orange boxes are problem points for which I can't seem to find any answer.
Here are the questions for which I seek guidance:
Is it possible to get a list of running tasks that same app initiated in a previous session?
Is it possible to associate some kind of meta data, such as a URL string, with a background task, so we can know which task is uploading which file?

Are you using NSURLConnection or NSURLSession?
If NSURLSession, you can use getTasksWithCompletionHandler. You can also use a background NSURLSessionConfiguration, rather than relying on beginBackgroundTaskWithExpirationHandler. And it terms of keeping track of the requests, you can retrieve the originalRequest from the task (and retrieve the URL from that), or update your model with the taskIdentifier for the NSURLSessionTask, and cross reference requests in your own model that way.

Related

NSURLSession and multithreading for downloading multiple files

I have an app on iOS which wants to download data from my server. The data is nothing but simple text files. I would like to download these files in parallel.
I am getting confused if I should be creating multiple instances of nsurlsession or multiple tasks(NSURLSessionTasks) under one nsurlsession.
I do know nsurlsession APIs are thread safe. And my fundamental confusion here is about following thing:
My NsurlsessionConfiguration is going to be same for the entire time. So, ideally I can use the same instance of NSURLSession for every file.
But does it make sense to create multiple instance of NSURLSessionTasks in parallel?
Or better approach is to take make a new NSURLSession for achieving the parallelism.
I am confused to understand if every NSURLSessionTask in the same NSURLSession creates a new end point or it is serialized.
One session with many tasks -- one per request -- will work safely concurrently. Whether the client makes requests across many servers or to a single one makes no difference so long as the concurrency is less than HTTPMaximumConnectionsPerHost which is part of the session config.
The tasks run in parallel. (It's actually pretty hard to find a place in the docs where those words appear explicitly, but there's pretty good implication here).
Notice that session configs have a delegateQueue. The rationale is -- because the sessions tasks are run concurrently -- the app level needs to serialize handling of the results, lest two parallel tasks step on each other via the delegate.

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.

Continue background task after 3 minutes

I tried the answer of this question
How can a connection started with NSURLConnection while in the foreground be continued in the background?
And works like a charm! but only for 3 minutes in background, then iOS sends the app to sleep, Im trying to upload n videos and 3 minutes is not enought to send all data, I also readed that I can change my NSURLConnection to NSURLSession but I have no idea how to start that or how much time it takes.
Is there a way to keep background task alive after 3 minutes and until upload ends?
Is there a way to keep background task alive after 3 minutes and until upload ends?
No, there isn't. On-going background execution is limited to very narrow purposes (e.g. playing music, VOIP apps, navigation apps, etc.). All of this is outlined in the Background Execution section of the App Programming Guide for iOS.
I also [read] that I can change my NSURLConnection to NSURLSession but I have no idea how to start that or how much time it takes.
Yes, this is correct. If you want network request to continue for more than a few minutes after the app enters background, you should use NSURLSession with a background NSURLSessionConfiguration. See Downloading Content in the Background in the App Programming Guide for iOS for more information. See WWDC 2013 What's New in Foundation Networking for introduction to NSURLSession, including a demonstration on how to do background session.
I'd suggest you tackle this in two steps: First, convert to NSURLSession, and second, enable background NSURLSession operation. I think you'll find this first step is pretty easy. All of the concepts are very familiar. The only trick is that NSURLSession has two types of interfaces, delegate-protocol pattern and completion handlers. If you plan on using this for background operation later, you'll want to stick with the delegate-protocol pattern because the simpler completion handler pattern is not compatible with background sessions. And you'll want to use the upload task method that uploads from a file (don't use the stream-based rendition).
This second step, enabling background operation, isn't hard, but it can be tricky, though, simply because it involves a few new little details that we haven't had to worry about in the past (e.g. the app delegate code to capture (and later call) the completion handler for background sessions). Also, when you start debugging background session code, it can be disorienting to have requests initiated by one debugging session to show up in the next debugging session (hey, they're background sessions that are supposed to keep running when your app is terminated, after all). It's not a problem, but can be a bit disorienting.

Async NSURLConnection triggering other Async NSURLConnection: what is the best way of doing this?

this is an open question aiming at understanding what is the best practice or the most common solution for a problem that I think might be common.
Let's say I have a list of URLs to download; the list is itself hosted on a server, so I start a NSURLConnection that downloads it. The code in connectionDidFinishLoading will use the list of URLs to instantiate one new NSURLConnection, asynchronously, per each URL; this in turn will trigger even more NSURLConnections, and so on - until there are no more URLs. See it as a tree of connections.
What is the best way to detect when all connections have finished?
I'm aiming the question to iOS7, but comments about other versions are welcome.
A couple of thoughts:
In terms of triggering the subsequent downloads after you retrieve the list from the server, just put the logic to perform those subsequent downloads inside the completion handler block (or completion delegate method) of the first request.
In terms of downloading a bunch of files, if targeting iOS 7 and later, you might consider using NSURLSession instead of NSURLConnection.
First, the downloading of files with a nice modest memory footprint is enabled by initiating "download" tasks (rather than "data" tasks).
Second, you can do the downloads using a background NSURLSessionConfiguration, which will let the downloads continue even if the user leaves the app. See the Downloading Content in the Background section of the App Programming Guide for iOS. There are a lot of i's that need dotting and t's that need crossing if you do this, but it's a great feature to consider implementing.
See WWDC 2013 What's New in Foundation Networking for an introduction to NSURLSession. Or see the relevent chapter of the URL Loading System Programming Guide.
In terms of keeping track of whether you're done, as Wain suggests, you can just keep track of the number of requests issued and the number of requests completed/failed, and in your "task completion" logic, just compare these two numbers, and initiate the "all done" logic if the number of completions matches the number of requests. There are a bunch of ways of doing this, somewhat dependent upon the details of your implementation, but hopefully this illustrates the basic idea.
Instead of using GCD you should consider using NSOperationQueue. You should also limit the number of concurrent operations, certainly on mobile devices, to perhaps 4 so you don't flood the network with requests.
Now, the number of operations on the queue is the remaining count. You can add a block to the end of each operation to check the queue count and execute any completion logic.
As Rob says in his answer you might want to consider NSURLSession rather than doing this yourself. It has a number of advantages.
Other options are building your own download manager class, or using a ready-made third party framework like AFNetworking. I've only worked with AFNetworking a little bit but from what I've seen its elegant, powerful, and easy to use.
Our company wrote an async download manager class based on NSURLConnection for a project that predates both AFNetworking and NSURLSession. It's not that hard, but it isn't as flexible as either NSURLSession or AFNetworking.

Using NSURLSession to download a lot of images

I have created a simple testing app to learn how to use NSURLSession. This App has to download images from a webservice and present them into a UITableView.
I've already written the first part of the App that reads a list of images urls from the web service, now, I want to display this list.
My doubt is:
given that the list of images could be a really long list, is it ok to create a NSURLSessionDownloadTask for each image?
I thought to create the session in the cellForRowAtIndexPath function and store the NSURLSessions in a NSDictionary using as key the IndexPath of the cell (and probably relying on NSURLCache to avoid to download the same images more than once).
Other solutions:
I can see three more solutions:
Using GCD with dispatch_async
Subclassing NSOperation and essentially store an NSOperation for any image I need to download.
Using a third party library like AFNetwork... but since it is a learning purpose app I prefer to go completely with my code
.
If the multiple NSURLSession isn't a good solution, I'd choose one of those options.
What do you think about this approach?
NSURLSessionTask is fine for a large number of downloads. One advantage of it over some of the other methods you mentioned is that downloads can be cancelled or paused. It also correctly implements concurrency for network operations, which is more difficult than many cats on the internet will lead you to believe (if you don't believe me, view the eskimo's 2010 WWDC session and sample code. NSOperation for network connections is not trivial).
NSURLSessionTask and friends are designed for exactly the kinds of problems you are trying to solve, and it's very well tested.
For a tableview, start the task in tableView:willDisplayCell:forRowAtIndexPath: and cancel (or pause) a task in tableView:didEndDisplayingCell:forRowAtIndexPath:. That will limit the active downloads to the currently visible cells.
Suggestion:
I also came across a similar situation were I need to download about 2000 Image files and 100 Video files. For that purpose I implemented a custom download manager using NSOperationQueue and blocks.
I have added this library to GitHub, please feel free to check the implementation.
IMO whilst it is ok to create an NSURLSessionTask for each image a standard first in first out implementation will cause problems when scrolling through your cells. The reason for this is that downloads will be queued on your NSURLSession and tasks will be executed in the order they've been added to the queue, in other words in a FIFO manner. Imagine a scenario where you've scrolled through a vast number of cells and you have to wait for all downloads to complete in order. You would not only have to wait a long time, you would be making unnecessary network requests for image assets that may no longer be relevant to your user.
Nick Lockwood created a great NSOperationQueue subclass called NSOperationStack that reverses the order of operations so that the the last operation is executed first (LIFO). IMO for a large number of downloads a LIFO implementation is a must.
NSOsperationStack is available here
If you combine this with an implementation that uses cellForRowAtIndexPath to initiate and NSURLCache to store downloads, you should end up with a very streamlined and efficient solution.
I would use (or at least take a look at) SDWebImage's SDWebImageManager.
Besides downloading you can set priority and continue in the background options which I think you'll want to have.

Resources