ios 将URLCache子类与URLSession一起使用

ewm0tg9j  于 2023-02-06  发布在  iOS
关注(0)|答案(1)|浏览(87)

我有一个应用程序,它使用基于URLSession的网络和URLCache在磁盘上存储网络请求。我注意到当URLCache的存储大小达到diskCapacity时,驱逐策略似乎是删除所有条目,这在我的用例中是个问题。因此,我决定编写一个URLCache子类,它将为缓存的响应提供自定义存储,并实现具有更好控制的LRU回收策略。
正如URLCache的文档所述,应该支持用于此目的的子类化:
URLCache类旨在按原样使用,但您可以在有特定需要时将其子类化。例如,您可能希望筛选缓存哪些响应,或者出于安全或其他原因重新实现存储机制。
但是,我在尝试将这个新的URLCache子类与URLSession联网一起使用时遇到了问题。
我使用HTTPGET获取了一个测试资源。响应头包含:

  • Cache-Control: public, max-age=30
  • Etag: <some-value>

使用标准的非子类URLCache时,第一个请求按预期从网络加载数据(使用HTTP代理验证)。如果第二个请求按预期在前30秒内完成,则不会转到网络。30秒后的后续请求按预期导致条件GETEtag
当使用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上测试。

ki1q1bka

ki1q1bka1#

在苹果的开发者论坛上,我收到了Quinn“爱斯基摩人”对我问题的回复:
我的经验是,对Foundation的URL加载系统类进行子类化会让你走上一条痛苦的道路[1]。如果我是你,我会在Foundation URL加载系统层之上进行自定义缓存。
[1]很久以前,Foundation URL加载系统是用Objective-C实现的,并且存在于Foundation框架中。在那个世界里,子类大多数都能工作。此后不久--我在这里说的是在介绍NSURLSession之前--核心实现改变了语言,转移到了CFNetwork。从那时起,你在Foundation中看到的类基本上是CFNetwork类型(私有的)的薄 Package 器。2除了对子类化的影响外,这通常是可以的。
因此,听起来应该阅读URLCache文档re:带着一点盐细分。

相关问题