我有一个应用程序,它使用基于URLSession
的网络和URLCache
在磁盘上存储网络请求。我注意到当URLCache
的存储大小达到diskCapacity
时,驱逐策略似乎是删除所有条目,这在我的用例中是个问题。因此,我决定编写一个URLCache
子类,它将为缓存的响应提供自定义存储,并实现具有更好控制的LRU回收策略。
正如URLCache的文档所述,应该支持用于此目的的子类化:
URLCache类旨在按原样使用,但您可以在有特定需要时将其子类化。例如,您可能希望筛选缓存哪些响应,或者出于安全或其他原因重新实现存储机制。
但是,我在尝试将这个新的URLCache
子类与URLSession
联网一起使用时遇到了问题。
我使用HTTP
GET
获取了一个测试资源。响应头包含:
Cache-Control: public, max-age=30
Etag: <some-value>
使用标准的非子类URLCache
时,第一个请求按预期从网络加载数据(使用HTTP代理验证)。如果第二个请求按预期在前30秒内完成,则不会转到网络。30秒后的后续请求按预期导致条件GET
和Etag
。
当使用URLCache
子类时,所有请求都从网络加载数据--最大年龄似乎无关紧要,并且不进行条件GET。URLCache
似乎在CachedURLResponse
示例从其内部存储加载后对它们做了一些特殊的事情,这会影响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上测试。
1条答案
按热度按时间ki1q1bka1#
在苹果的开发者论坛上,我收到了Quinn“爱斯基摩人”对我问题的回复:
我的经验是,对Foundation的URL加载系统类进行子类化会让你走上一条痛苦的道路[1]。如果我是你,我会在Foundation URL加载系统层之上进行自定义缓存。
[1]很久以前,Foundation URL加载系统是用Objective-C实现的,并且存在于Foundation框架中。在那个世界里,子类大多数都能工作。此后不久--我在这里说的是在介绍NSURLSession之前--核心实现改变了语言,转移到了CFNetwork。从那时起,你在Foundation中看到的类基本上是CFNetwork类型(私有的)的薄 Package 器。2除了对子类化的影响外,这通常是可以的。
因此,听起来应该阅读URLCache文档re:带着一点盐细分。