Downloading files in background with URLSessionDownloadTask #Swift #Xcode #Download Progress ## iOS ##
This snippet demonstrates how to use URLSessionDownloadTask to download files in background so that they can completed even if the app is terminated. It also shows how to implement progress monitoring for multiple tasks running in parallel
Starting downloads
To start a download that can be completed in background, even if the app is terminated, create a URLSessionConfiguration for background processing. The identifier will identify the URLSession: if the process is terminated and later restarted, you can get the “same” URLSession f.e. to ask about the progress of downloads in progress:
// Create downloadsSession here, to set self as delegate
lazy var downloadsSession: URLSession = {
// let configuration = URLSessionConfiguration.default
let configuration = URLSessionConfiguration.background(withIdentifier: “bgSessionConfiguration”)
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
Then create a URLSession object. This can be observed using a URLSessionTaskDelegate.
But, beware: If an URLSession still exists from a previous download in the same process, it doesn’t create a new URLSession object but returns the existing one with the old delegate object attached! It will give you a warning about this behaviour “A background URLSession with identifier … already exists!”:
Then create a URLSessionDownloadTask:
let url = URL(string: “https://example.com/example.pdf")!
let task = session.downloadTask(with: url) task.resume()
Observing started downloads
Completion handler blocks are not supported in background sessions — the URLSession delegate has to be used for observing the download. When the download is completed, it is written to the Caches directory of the application and the URLSessionDownloadDelegate is notified:
class SomeClass : NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64){
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
debugPrint(“Progress \(downloadTask) \(progress)”)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
debugPrint(“Download finished: \(location)”)
try? FileManager.default.removeItem(at: location)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint(“Task completed: \(task), error: \(error)”)
}
}
If you stop the application while a download is still in progress and run it again, by accessing URLSession with the same identifier, you can get notified about the progress of the downloads and retrieve a notification for all background tasks that have been completed in background. But be aware: the system cancels all background tasks when an application is terminated by swiping up from the app switcher:
Progress monitoring for multiple tasks
Progress monitoring for multiple tasks
To display the progress of all running tasks, use the delegate method urlSession:downloadTask:didWriteData:. If multiple tasks are running in parallel, you can use URLSession#getTasksWithCompletionHandler to compute the total of all running tasks:
{
guard let url = downloadTask.originalRequest?.url,
let download = downloadService.activeDownloads[url] else { return }
// 2
download.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
// 3
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite,
countStyle: .file)
// 4
DispatchQueue.main.async {
if let trackCell = self.tableView.cellForRow(at: IndexPath(row: download.track.index,section: 0)) as? TrackCell {
trackCell.updateDisplay(progress: download.progress, totalSize: totalSize)
}
}
}
You can find the working code here if you weren’t using Cocoapods. Please let me know if you have any questions!
// Updates progress infofunc urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,didWriteData bytesWritten: Int64, totalBytesWritten: Int64,totalBytesExpectedToWrite: Int64)
{
guard let url = downloadTask.originalRequest?.url,
let download = downloadService.activeDownloads[url] else { return }
// 2
download.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
// 3
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite,
countStyle: .file)
// 4
DispatchQueue.main.async {
if let trackCell = self.tableView.cellForRow(at: IndexPath(row: download.track.index,section: 0)) as? TrackCell {
trackCell.updateDisplay(progress: download.progress, totalSize: totalSize)
}
}
}
You can find the working code here if you weren’t using Cocoapods. Please let me know if you have any questions!

Comments
Post a Comment