Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active August 21, 2024 11:18
Show Gist options
  • Save steipete/a7b46f36bc1a341bfe26897d964169af to your computer and use it in GitHub Desktop.
Save steipete/a7b46f36bc1a341bfe26897d964169af to your computer and use it in GitHub Desktop.
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!

@nickruddeni
Copy link

Do you think you could avoid the automatic cache purging for certain more critical resources by specifying the directory to be the document directory instead of Caches?

@Vyeczorny
Copy link

HI @steipete, thanks for great article 👏 I'm wondering about on thing. In line 48 you check if doesn't have entry for given request, but what if we already had old version of response cached and we want to substitute it with the recent one? Is it guaranteed that URLSession removes old data before invoking completion?

@steipete
Copy link
Author

It’s been 5 years since this - I would not assume any of this is still accurate. This needs a re-test. I don’t do iOS work these days, so someone else could pick this up.

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