URLSession Tutorial: Getting Started
In this URLSession tutorial, you’ll learn how to create HTTP requests as well as implement background downloads that can be both paused and resumed. By Felipe Laso-Marsetti.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
URLSession Tutorial: Getting Started
30 mins
- Getting Started
- URLSession Overview
- Understanding Session Task Types
- DataTask and DownloadTask
- Downloading Classes
- URLSession Delegates
- Downloading a Track
- Saving and Playing the Track
- Pausing, Resuming, and Canceling Downloads
- Canceling Downloads
- Pausing Downloads
- Resuming Downloads
- Showing and Hiding the Pause/Resume and Cancel Buttons
- Showing Download Progress
- Displaying the Download’s Progress
- Enabling Background Transfers
- Relaunching Your App
- Testing Your App’s Functionality
- Where to Go From Here?
Pausing, Resuming, and Canceling Downloads
What if the user wants to pause a download or to cancel it altogether? In this section, you’ll implement the pause, resume and cancel features to give the user complete control over the download process.
You’ll start by allowing the user to cancel an active download.
Canceling Downloads
In DownloadService.swift, add the following code inside cancelDownload(_:)
:
guard let download = activeDownloads[track.previewURL] else {
return
}
download.task?.cancel()
activeDownloads[track.previewURL] = nil
To cancel a download, you’ll retrieve the download task from the corresponding Download
in the dictionary of active downloads and call cancel()
on it to cancel the task. You’ll then remove the download object from the dictionary of active downloads.
Pausing Downloads
Your next task is to let your users pause their downloads and come back to them later.
Pausing a download is similar to canceling it. Pausing cancels the download task, but also produces resume data, which contains enough information to resume the download at a later time if the host server supports that functionality.
Replace the contents of pauseDownload(_:)
with the following code:
guard
let download = activeDownloads[track.previewURL],
download.isDownloading
else {
return
}
download.task?.cancel(byProducingResumeData: { data in
download.resumeData = data
})
download.isDownloading = false
The key difference here is that you call cancel(byProducingResumeData:)
instead of cancel()
. You provide a closure parameter to this method, which lets you save the resume data to the appropriate Download
for future resumption.
You also set the isDownloading
property of the Download
to false
to indicate that the user has paused the download.
Now that the pause function is complete, the next order of business is to allow the user to resume a paused download.
Resuming Downloads
Replace the content of resumeDownload(_:)
with the following code:
guard let download = activeDownloads[track.previewURL] else {
return
}
if let resumeData = download.resumeData {
download.task = downloadsSession.downloadTask(withResumeData: resumeData)
} else {
download.task = downloadsSession
.downloadTask(with: download.track.previewURL)
}
download.task?.resume()
download.isDownloading = true
When the user resumes a download, you check the appropriate Download
for the presence of resume data. If found, you’ll create a new download task by invoking downloadTask(withResumeData:)
with the resume data. If the resume data is absent for any reason, you’ll create a new download task with the download URL.
In either case, you’ll start the task by calling resume
and set the isDownloading
flag of the Download
to true
to indicate the download has resumed.
Showing and Hiding the Pause/Resume and Cancel Buttons
There’s only one item left to do for these three functions to work: You need to show or hide the Pause/Resume and Cancel buttons, as appropriate.
To do this, TrackCell
‘s configure(track:downloaded:)
needs to know if the track has an active download and whether it’s currently downloading.
In TrackCell.swift, change configure(track:downloaded:)
to configure(track:downloaded:download:)
:
func configure(track: Track, downloaded: Bool, download: Download?) {
In SearchViewController.swift, fix the call in tableView(_:cellForRowAt:)
:
cell.configure(track: track,
downloaded: track.downloaded,
download: downloadService.activeDownloads[track.previewURL])
Here, you extract the track’s download object from activeDownloads
.
Back in TrackCell.swift, locate // TODO 14
in configure(track:downloaded:download:)
and add the following property:
var showDownloadControls = false
Then replace // TODO 15
with the following:
if let download = download {
showDownloadControls = true
let title = download.isDownloading ? "Pause" : "Resume"
pauseButton.setTitle(title, for: .normal)
}
As the comment notes, a non-nil download object means a download is in progress, so the cell should show the download controls: Pause/Resume and Cancel. Since the pause and resume functions share the same button, you’ll toggle the button between the two states, as appropriate.
Below this if-closure, add the following code:
pauseButton.isHidden = !showDownloadControls
cancelButton.isHidden = !showDownloadControls
Here, you show the buttons for a cell only if a download is active.
Finally, replace the last line of this method:
downloadButton.isHidden = downloaded
with the following code:
downloadButton.isHidden = downloaded || showDownloadControls
Here, you tell the cell to hide the Download button if the track is downloading.
Build and run your project. Download a few tracks concurrently and you’ll be able to pause, resume and cancel them at will:
Showing Download Progress
At this point, the app is functional, but it doesn’t show the progress of the download. To improve the user experience, you’ll change your app to listen for download progress events and display the progress in the cells. There’s a session delegate method that’s perfect for this job!
First, in TrackCell.swift, replace // TODO 16
with the following helper method:
func updateDisplay(progress: Float, totalSize : String) {
progressView.progress = progress
progressLabel.text = String(format: "%.1f%% of %@", progress * 100, totalSize)
}
The track cell has progressView
and progressLabel
outlets. The delegate method will call this helper method to set their values.
Next, in SearchViewController.swift, add the following delegate method to the URLSessionDownloadDelegate
extension:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
// 1
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)
}
}
}
Looking through this delegate method, step-by-step:
- You extract the URL of the provided
downloadTask
and use it to find the matchingDownload
in your dictionary of active downloads. - The method also provides the total bytes you have written and the total bytes you expect to write. You calculate the progress as the ratio of these two values and save the result in
Download
. The track cell will use this value to update the progress view. -
ByteCountFormatter
takes a byte value and generates a human-readable string showing the total download file size. You’ll use this string to show the size of the download alongside the percentage complete. - Finally, you find the cell responsible for displaying the
Track
and call the cell’s helper method to update its progress view and progress label with the values derived from the previous steps. This involves the UI, so you do it on the main queue.