In this episode we create a DownloadInfo model in CoreData in order to track the state of a download, separate from any view controller.
Episode Links Source Code Creating a Model to track Download State import Foundation import CoreData enum DownloadStatus : String { case Pending case Downloading case Paused case Failed case Completed } @objc(DownloadInfo) class DownloadInfo : NSManagedObject { var status: DownloadStatus? { get { guard let value = statusValue else { return nil } return DownloadStatus(rawValue: value) } set { statusValue = status?.rawValue } } static var offlineLocation: URL { let docsDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! return URL(fileURLWithPath: docsDir).appendingPathComponent(".downloads", isDirectory: true) } } Create the model in a Pending State class DownloadController { // ... func download(episode: Episode) { guard let videoURL = episode.videoURL else { return } let context = PersistenceManager.sharedContainer.viewContext let downloadInfo = DownloadInfo(context: context) downloadInfo.downloadedAt = NSDate() downloadInfo.status = .Pending downloadInfo.episode = episode downloadInfo.progress = 0 try! context.save() let operation = DownloadOperation(url: videoURL, episodeID: Int(episode.id)) downloadQueue.addOperation(operation) } } Updating the model during the download class DownloadOperation { // ... lazy var downloadInfo: DownloadInfo! = { let fetchRequest: NSFetchRequest<DownloadInfo> = DownloadInfo.fetchRequest() fetchRequest.predicate = NSPredicate(format: "episode.id = %d", self.episodeID) fetchRequest.fetchLimit = 1 return try! self.context.fetch(fetchRequest).first! }() override func execute() { downloadInfo.status = .Downloading try! PersistenceManager.save(context: context) downloadTask = session.downloadTask(with: url) downloadTask?.resume() } // ... func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { // ... downloadInfo.progress = progress downloadInfo.sizeInBytes = totalBytesWritten // ... } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { print("Did complete download") if let e = error as? NSError { print("Error: \(e)") if e.domain == NSURLErrorDomain && e.code == NSURLErrorCancelled { downloadInfo.status = .Paused } else { downloadInfo.status = .Failed } } else { downloadInfo.status = .Completed } try! PersistenceManager.save(context: context) finish() } // ... } Moving the downloaded file to a permanent location func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { print("File downloaded to: \(location)") let ext = downloadTask.currentRequest?.url?.pathExtension ?? "mp4" let uuid = UUID() let dir = DownloadInfo.offlineLocation if !FileManager.default.fileExists(atPath: dir.path) { try! FileManager.default.createDirectory(at: dir, withIntermediateDirectories: false, attributes: nil) } let filename = "\(uuid.uuidString).\(ext)" let targetLocation = dir.appendingPathComponent(filename) try! FileManager.default.moveItem(at: location, to: targetLocation) let attribs = try! FileManager.default.attributesOfItem(atPath: targetLocation.path) downloadInfo.progress = 1.0 if let sizeNumber = attribs[FileAttributeKey.size] as? NSNumber { downloadInfo.sizeInBytes = sizeNumber.int64Value } downloadInfo.path = targetLocation.path }