Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active June 21, 2022 05:39
Embed
What would you like to do?
Using URLCache with download tasks (NSURLCache & NSURLSessionDownloadTask)
import Foundation
import os.log
class URLCacheTest {
let logger = Logger(subsystem: "URLCacheTest", category: "main")
// HTTP HEADERS:
// Date: Wed, 04 Nov 2020 11:13:24 GMT
// Server: Apache
// Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
// X-Content-Type-Options: nosniff
// X-Frame-Options: SAMEORIGIN
// Last-Modified: Sun, 19 May 2002 14:49:00 GMT
// Accept-Ranges: bytes
// Content-Length: 20702285
// Content-Type: application/pdf
let flightPlanURL = URL(string: "https://www.hq.nasa.gov/alsj/a17/A17_FlightPlan.pdf")!
// Custom URL cache with 1 GB disk storage
lazy var cache: URLCache = {
let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let diskCacheURL = cachesURL.appendingPathComponent("DownloadCache")
let cache = URLCache(memoryCapacity: 100_000_000, diskCapacity: 1_000_000_000, directory: diskCacheURL)
logger.info("Cache path: \(diskCacheURL.path)")
return cache
}()
// Custom URLSession that uses our cache
lazy var session: URLSession = {
let config = URLSessionConfiguration.default
config.urlCache = cache
return URLSession(configuration: config)
}()
init() {
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let targetURL = documentURL.appendingPathComponent(flightPlanURL.lastPathComponent)
downloadFile(remoteURL: flightPlanURL, targetURL: targetURL)
}
func downloadFile(remoteURL: URL, targetURL: URL) {
let request = URLRequest(url: remoteURL)
let downloadTask = session.downloadTask(with: request) { url, response, error in
self.logger.info("Download Task complete")
// Store data in cache
if let response = response, let url = url,
self.cache.cachedResponse(for: request) == nil,
let data = try? Data(contentsOf: url, options: [.mappedIfSafe]) {
self.cache.storeCachedResponse(CachedURLResponse(response: response, data: data), for: request)
}
// Move file to target location
guard let tempURL = url else { return }
_ = try? FileManager.default.replaceItemAt(targetURL, withItemAt: tempURL)
}
downloadTask.resume()
}
}
@ArkadiGiniApps
Copy link

ArkadiGiniApps commented Nov 15, 2020

A small side note, using URLSession with background configuration will ignore the URLCache object, its a "minor" not well documented fact. I had it confirmed with a ATS a few years back as far as i know its still there, and very poorly documented

@steipete
Copy link
Author

Indeed. I mentioned this in the blog post.

@steve-mnp
Copy link

Thanks for this excellent article and accompanying gist!

@fumoboy007
Copy link

Hmm doesn’t this defeat the main benefit of a download task, which is to avoid loading the entire file contents into memory?

@steipete
Copy link
Author

The data is memory mapped, it is not actually loaded into memory. This approach works even for multi-GB-files.

@fumoboy007
Copy link

Oooh whoops missed the .mappedIfSafe. Thanks, @steipete!

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