我有一个应用程序,它使用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秒后的后续请求导致有条件的GETEtag.

当使用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上测试.

推荐答案

在苹果开发者论坛上,我收到了奎因对我的问题的回应,这位爱斯基摩人说:

我的经验是,对Foundation的URL加载系统类进行子类化会让您走上痛苦的道路[1].如果我处于您的位置,我会在Foundation URL加载系统层之上执行您的定制缓存.

[1]很久以前,基金会的URL加载系统是在Objective-C中实现的,并且存在于基金会框架中.在那个世界里,子阶级基本上是管用的.不久之后--我在这里说的是在引入NSURLSession之前--核心实现改变了语言并转移到CFNetwork.从那时起,您在Foundation中看到的类基本上都是(私有)CFNetwork类型的薄包装器.这通常是可以的,除了对子类化的影响.

因此,听起来人们应该重新阅读URLCache文档:对子类化持保留态度.

Ios相关问答推荐

在SWIFT中将圆角+透明背景从导入视频(.mp4)添加到导出视频(.mov)

在iOS中禁用URLSession自动重试机制

当S没有身体时该如何处理

为什么EKEventStore().questFullAccessToEvents()可以与模拟器一起使用,而不能与真实设备一起使用?

ScrollViewReader Scrollto Not Working

如何在iOS 17 SwiftUI中获取用户名

拖动手势导致 sim 崩溃 Swift UI

为什么 Firebase Messaging 配置对于 IOS native 和 Flutter iOS 不同?

如何解决错误 HE0004:无法在 Visual Studio 2022 中加载框架ContentDeliveryServices

长按 SwiftUI 更改项目的顺序

为什么@FetchRequest 在删除时不更新和重绘视图?

具有线性渐变的 SVG textPath 在 Safari 中损坏

我无法使用 Swift 将文件上传到 firebase

如何根据条件快速将返回类型设为 LinearGradient 或 AngularGradient?

SwiftUI DatePicker 格式在每个设备上都不一样,如何让它总是显示相同的 Select 版本?

如何忽略touch 事件并将它们传递给另一个子视图的 UIControl 对象?

iPhone 未连接.连接 iPhone 后 Xcode 将继续

为所有 UIImageViews 添加圆角

从 Swift 转换为 Objective-c 的工具

UIGestureRecognizer 阻止子视图处理touch 事件