Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Last active January 30, 2019 17:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertmryan/fd52d2dcc4cdbb8632d1bf59f598a342 to your computer and use it in GitHub Desktop.
Save robertmryan/fd52d2dcc4cdbb8632d1bf59f598a342 to your computer and use it in GitHub Desktop.
//
// FileDownloader.swift
//
// Created by Robert Ryan on 1/30/19.
//
import Foundation
import os.log
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "FileDownloader")
class FileDownloader {
private static let downloadsSession = URLSession(configuration: .default)
private weak var task: URLSessionDownloadTask?
private let url: URL
init(url: URL) {
self.url = url
}
/// Start download
public func startDownload() {
let task = FileDownloader.downloadsSession.downloadTask(with: url) { location, response, error in
self.saveDownload(originalURL: self.url, at: location, response: response, error: error)
}
task.resume()
self.task = task
}
/// Cancel download
func cancel() {
task?.cancel()
}
}
private extension FileDownloader {
/// Once download is done, save the resulting file if it was successful.
///
/// This will save it to the "documents"
///
/// - Parameters:
/// - originalURL: The original URL of the download request.
/// - location: The temporary location of the the downloaded file to be saved.
/// - response: The `URLResponse` of the download request.
/// - error: The `Error` of the download request.
func saveDownload(originalURL: URL, at location: URL?, response: URLResponse?, error: Error?) {
guard error == nil, let location = location else {
os_log("error %{PUBLIC}@", log: log, type: .error, error?.localizedDescription ?? "Unknown error")
return
}
let destination = localCachesURL(for: originalURL)
let fileManager = FileManager.default
try? fileManager.removeItem(at: destination)
do {
try fileManager.copyItem(at: location, to: destination)
os_log("Save was completed at %{PUBLIC}@ from %{PUBLIC}@", log: log, type: .debug, destination.absoluteString, originalURL.absoluteString)
} catch let error {
os_log("Could not copy file to disk: %{PUBLIC}@", log: log, type: .error, error.localizedDescription)
}
}
/// The local directory to save the file.
///
/// - Parameter url: The URL of the original request.
///
/// - Returns: The URL in the local file system
func localCachesURL(for url: URL) -> URL {
return try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(url.lastPathComponent)
}
}
@robertmryan
Copy link
Author

robertmryan commented Jan 30, 2019

This includes a few changes:

  1. Use static for URLSession so that we’re not instantiating a new one for every download. URLSession has some overhead, so you really only want one for all of your downloads.

  2. Make task property weak. We don’t need to hang on it it after the download is done. Obviously, in startDownload that means that we need to temporarily save the task to a local var, resume it, and only then set the ivar. But this ensures that when the task is completed, it is released. Not critical, but best practice.

  3. You were saving to the downloads folder. Apple advises that this folder is (a) only for files that are user documents; and (b) cannot be easily re-retrieved. Downloads are usually saved to caches folder.

  4. I would not throw assertion if the download failed. E.g. do you really want app to crash if the web server is temporarily unavailable?

  5. The only reason you’d save the task to an ivar is if you were going to reference it later. So I added typical use-case, e.g. a cancel method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment