我有一个应用程序,它使用URLSession
为基础的网络和URLCache
存储在磁盘上的网络请求.我注意到,当URLCache
的存储大小达到diskCapacity
时,驱逐策略似乎是删除所有条目,这在我的用例中是一个问题.因此,我决定编写一个URLCache
子类,它将为缓存的响应提供自定义存储,并以更好的控制实现LRU逐出策略.
正如URLCache的文档所述,应该支持用于此目的的子类化:
URLCache类应该按原样使用,但当您有特定需求时,您可以将其子类.例如,您可能希望筛选缓存哪些响应,或者出于安全或其他原因重新实现存储机制.
然而,我在try 使用这个新的URLCache
子类和URLSession
网络时遇到了问题.
我有一个使用HTTP
GET
获取的测试资源.响应标头包含:
-
Cache-Control: public, max-age=30
个 -
Etag: <some-value>
个
当使用标准的非子类URLCache
时,第一个请求按照预期从网络加载数据(使用HTTP代理进行验证).第二个请求如果像预期的那样在第一个30秒内完成,则不会进入网络.如预期的那样,30秒后的后续请求导致有条件的GET
和Etag
.
当使用URLCache
个子类时,所有请求都从网络加载数据-最长时间似乎无关紧要,并且不会进行条件获取.
从内部存储中加载CachedURLResponse
个实例后,URLCache
似乎会对它们做一些特殊的事情,这会影响URLSession
处理HTTP缓存逻辑的方式.我错过了什么吗?
我已经编写了一个非常小的URLCache
个子类实现来演示这个问题.此类使用NSKeyedArchiver
/NSKeyedUnarchiver
存储和加载CachedURLResponse
个实例,并且它只支持零或一个响应.请注意,由于我想使用自己的存储空间,所以没有调用Super--这是设计好的.
class CustomURLCache: URLCache {
let cachedResponseFileURL = URL(filePath: NSTemporaryDirectory().appending("entry.data"))
// MARK: Internal storage
func read() -> CachedURLResponse? {
guard let data = try? Data(contentsOf: cachedResponseFileURL) else { return nil }
return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! CachedURLResponse
}
func store(_ cachedResponse: CachedURLResponse) {
try! (try! NSKeyedArchiver.archivedData(withRootObject: cachedResponse, requiringSecureCoding: false)).write(to: cachedResponseFileURL)
}
// MARK: URLCache Overrides
override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
read()
}
override func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: @escaping (CachedURLResponse?) -> Void) {
completionHandler(read())
}
override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) {
store(cachedResponse)
}
override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for dataTask: URLSessionDataTask) {
store(cachedResponse)
}
}
我的测试用例:
func test() {
let useEvictingCache = false
let config = URLSessionConfiguration.default
if useEvictingCache {
config.urlCache = CustomURLCache()
} else {
config.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 1024 * 1024 * 100)
}
self.urlSession = URLSession(configuration: config)
let url = URL(string: "https://example.com/my-test-resource")!
self.urlSession?.dataTask(with: URLRequest(url: url), completionHandler: { data, response, error in
if let data {
print("GOT DATA with \(data.count) bytes")
} else if let error {
print("GOT ERROR \(error)")
}
}).resume()
}
在iOS 16.2上测试.