
This video is only available to subscribers. Start a subscription today to get access to this and 469 other videos.
File Downloads - Part 1
This episode is part of a series: Large File Downloads.
This episode utilizes Swift 3 and Xcode 8.
Episode Links
Decoupled from View Controllers
File downloads should be decoupled from view controllers, so the user is free to navigate around while the file is being downloaded. To accomplish this, we will do our download in an Operation
:
class DownloadOperation : BaseOperation, URLSessionDownloadDelegate {
let url: URL
let episodeID: Int
let sessionConfiguration = URLSessionConfiguration.default
lazy var session: URLSession = {
let session = URLSession(configuration: self.sessionConfiguration,
delegate: self,
delegateQueue: nil)
return session
}()
var downloadTask: URLSessionDownloadTask?
init(url: URL, episodeID: Int) {
self.url = url
self.episodeID = episodeID
}
override func execute() {
downloadTask = session.downloadTask(with: url)
downloadTask?.resume()
}
override func cancel() {
downloadTask?.cancel()
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
let userInfo: [String: Any] = [
"episodeID" : episodeID,
"progress" : progress,
"totalBytesWritten" : totalBytesWritten,
"totalBytesExpectedToWrite" : totalBytesExpectedToWrite
]
DispatchQueue.main.async {
NotificationCenter.default.post(name: .downloadProgress,
object: self,
userInfo: userInfo)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Did complete. error? \(error)")
finish()
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("File downloaded to: \(location)")
}
}
Now the question becomes, where do we enqueue this operation? Who will own the queue?.
I decided to create a DownloadController
that we can access from multiple places to maintain the queue:
class DownloadController {
let downloadQueue: OperationQueue
static let shared = DownloadController()
private init() {
downloadQueue = OperationQueue()
downloadQueue.maxConcurrentOperationCount = 1
}
func download(episode: Episode) {
guard let videoURL = episode.videoURL else { return }
let operation = DownloadOperation(url: videoURL, episodeID: Int(episode.id))
downloadQueue.addOperation(operation)
}
}
With that in place, now we can kick off the download and report on progress in our view controller:
func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(EpisodeViewController.onDownloadProgress(notification:)), name: .downloadProgress, object: nil)
}
func onDownloadProgress(notification: Notification) {
guard let progress = notification.userInfo?["progress"] as? Float,
let id = notification.userInfo?["episodeID"] as? Int,
id == Int(episode.id)
else {
return
}
let formattedProgress = String(format: "%.1f%%", progress * 100.0)
progressLabel.text = formattedProgress
progressView.progress = progress
}
@IBAction func downloadTapped(_ sender: AnyObject) {
DownloadController.shared.download(episode: episode)
...
}