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.

4.7 (58) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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 for Download.

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(_:):

  1. You first initialize a Download with the track.
  2. Using your new session object, you create a URLSessionDownloadTask with the track’s preview URL and set it to the task property of the Download.
  3. You start the download task by calling resume() on it.
  4. You indicate that the download is in progress.
  5. Finally, you map the download URL to its Download in activeDownloads.

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:

  1. You extract the original request URL from the task, look up the corresponding Download in your active downloads and remove it from that dictionary.
  2. You then pass the URL to localFilePath(for:), which generates a permanent local file path to save to by appending the lastPathComponent of the URL (the file name and extension of the file) to the path of the app’s Documents directory.
  3. 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’s downloaded property to true.
  4. 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:

Half Tunes App With Music Player Running