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?
Downloading Classes
The first thing you’ll need to do to handle multiple downloads is to create a custom object to hold the state of an active download.
Create a new Swift file named Download.swift in the Model group.
Open Download.swift, and add the following implementation below the Foundation import
:
class Download {
var isDownloading = false
var progress: Float = 0
var resumeData: Data?
var task: URLSessionDownloadTask?
var track: Track
init(track: Track) {
self.track = track
}
}
Here’s a rundown of the properties of Download
:
- isDownloading: Whether the download is ongoing or paused.
- progress: The fractional progress of the download, expressed as a float between 0.0 and 1.0.
-
resumeData: Stores the
Data
produced when the user pauses a download task. If the host server supports it, your app can use this to resume a paused download. -
task: The
URLSessionDownloadTask
that downloads the track. -
track: The track to download. The track’s
url
property also acts as a unique identifier forDownload
.
Next, in DownloadService.swift, replace // TODO 4
with the following property:
var activeDownloads: [URL: Download] = [:]
This dictionary will maintain a mapping between a URL and its active Download
, if any.
URLSession Delegates
You could create your download task with a completion handler, as you did when you created the data task. However, later in this tutorial you’ll check and update the download progress, which requires you to implement a custom delegate. So you might as well do that now.
There are several session delegate protocols, listed in Apple’s URLSession documentation. URLSessionDownloadDelegate
handles task-level events specific to download tasks.
You’re going to need to set SearchViewController
as the session delegate soon, so now you’ll create an extension to conform to the session delegate protocol.
Open SearchViewController.swift and replace // TODO 5
with the following URLSessionDownloadDelegate
extension below:
extension SearchViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
print("Finished downloading to \(location).")
}
}
The only non-optional URLSessionDownloadDelegate
method is urlSession(_:downloadTask:didFinishDownloadingTo:)
, which your app calls when a download finishes. For now, you’ll print a message whenever a download completes.
Downloading a Track
With all the preparatory work out of the way, you’re now ready to put file downloads in place. Your first step is to create a dedicated session to handle your download tasks.
In SearchViewController.swift, replace // TODO 6
with the following code:
lazy var downloadsSession: URLSession = {
let configuration = URLSessionConfiguration.default
return URLSession(configuration: configuration,
delegate: self,
delegateQueue: nil)
}()
Here, you initialize a separate session with a default configuration and specify a delegate which lets you receive URLSession
events via delegate calls. This will be useful for monitoring the progress of the task.
Setting the delegate queue to nil
causes the session to create a serial operation queue to perform all calls to delegate methods and completion handlers.
Note the lazy creation of downloadsSession
; this lets you delay the creation of the session until after you initialize the view controller. Doing that allows you to pass self
as the delegate parameter to the session initializer.
Now replace // TODO 7
at the end of viewDidLoad()
with the following line:
downloadService.downloadsSession = downloadsSession
This sets the downloadsSession
property of DownloadService
to the session you just defined.
With your session and delegate configured, you’re finally ready to create a download task when the user requests a track download.
In DownloadService.swift, replace the content of startDownload(_:)
with the following implementation:
// 1
let download = Download(track: track)
// 2
download.task = downloadsSession.downloadTask(with: track.previewURL)
// 3
download.task?.resume()
// 4
download.isDownloading = true
// 5
activeDownloads[download.track.previewURL] = download
When the user taps a table view cell’s Download button, SearchViewController
, acting as TrackCellDelegate
, identifies the Track
for this cell, then calls startDownload(_:)
with that Track
.
Here’s what’s going on in startDownload(_:)
:
- You first initialize a
Download
with the track. - Using your new session object, you create a
URLSessionDownloadTask
with the track’s preview URL and set it to thetask
property of theDownload
. - You start the download task by calling
resume()
on it. - You indicate that the download is in progress.
- Finally, you map the download URL to its
Download
inactiveDownloads
.
Build and run your app, search for any track and tap the Download button on a cell. After a while, you’ll see a message in the debug console signifying that the download is complete.
Finished downloading to file:///Users/mymac/Library/Developer/CoreSimulator/Devices/74A1CE9B-7C49-46CA-9390-3B8198594088/data/Containers/Data/Application/FF0D263D-4F1D-4305-B98B-85B6F0ECFE16/tmp/CFNetworkDownload_BsbzIk.tmp.
The Download button is still showing, but you’ll fix that soon. First, you want to play some tunes!
Saving and Playing the Track
When a download task completes, urlSession(_:downloadTask:didFinishDownloadingTo:)
provides a URL to the temporary file location, as you saw in the print message. Your job is to move it to a permanent location in your app’s sandbox container directory before you return from the method.
In SearchViewController.swift, replace the print statement in urlSession(_:downloadTask:didFinishDownloadingTo:)
with the following code:
// 1
guard let sourceURL = downloadTask.originalRequest?.url else {
return
}
let download = downloadService.activeDownloads[sourceURL]
downloadService.activeDownloads[sourceURL] = nil
// 2
let destinationURL = localFilePath(for: sourceURL)
print(destinationURL)
// 3
let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationURL)
do {
try fileManager.copyItem(at: location, to: destinationURL)
download?.track.downloaded = true
} catch let error {
print("Could not copy file to disk: \(error.localizedDescription)")
}
// 4
if let index = download?.track.index {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadRows(at: [IndexPath(row: index, section: 0)],
with: .none)
}
}
Here’s what you’re doing at each step:
- You extract the original request URL from the task, look up the corresponding
Download
in your active downloads and remove it from that dictionary. - You then pass the URL to
localFilePath(for:)
, which generates a permanent local file path to save to by appending thelastPathComponent
of the URL (the file name and extension of the file) to the path of the app’s Documents directory. - Using
fileManager
, you move the downloaded file from its temporary file location to the desired destination file path, first clearing out any item at that location before you start the copy task. You also set the download track’sdownloaded
property totrue
. - Finally, you use the download track’s
index
property to reload the corresponding cell.
Build and run your project, run a query, then pick any track and download it. When the download has finished, you’ll see the file path location printed to your console:
file:///Users/mymac/Library/Developer/CoreSimulator/Devices/74A1CE9B-7C49-46CA-9390-3B8198594088/data/Containers/Data/Application/087C38CC-0CEB-4895-ADB6-F44D13C2CA5A/Documents/mzaf_2494277700123015788.plus.aac.p.m4a
The Download button disappears now, because the delegate method set the track’s downloaded
property to true
. Tap the track and you’ll hear it play in the AVPlayerViewController
as shown below: