ios - 使用storeCachedResponse存储在缓存中后,未检索到URLresponse

标签 ios http caching nsurlsession nsurlcache

目标

我正在尝试将URLRequest中的数据/响应注入(inject)到我的缓存中的另一个URLRequest中。

设置

这只是一个示例代码。准备好将其转储到项目中了。

我想要做的是使用从landscapeURLString网络请求中检索到的响应+数据...将存储在我 session 的缓存中的lizardURLString 请求。

import UIKit

class ViewController: UIViewController {

    lazy var defaultSession : URLSession = {
        let urlCache = URLCache(memoryCapacity: 500 * 1024 * 1024, diskCapacity: 500 * 1024 * 1024, diskPath: "something")
        let configuration = URLSessionConfiguration.default
        configuration.urlCache = urlCache
        let session = URLSession(configuration: configuration)

        return session
    }()
    lazy var downloadLizzardbutton : UIButton = {
        let btn = UIButton()
        btn.translatesAutoresizingMaskIntoConstraints = false
        btn.setTitle("download lizard image OFFLINE", for: .normal)
        btn.backgroundColor = .blue
        btn.addTarget(self, action: #selector(downloadLizardAction), for: .touchUpInside)
        return btn
    }()

    let imageView : UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()

    // I make sure my internet is set to OFF so that it forces this to be read from cache...
    @objc func downloadLizardAction() {
        downloadImage(from: lizardURLString, from: defaultSession)
    }
    let lizardURLString = "https://upload.wikimedia.org/wikipedia/commons/e/e0/Large_Scaled_Forest_Lizard.jpg"
    let landscapeURLString = "https://images.pexels.com/photos/414171/pexels-photo-414171.jpeg"        

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(imageView)
        view.addSubview(downloadLizzardbutton)
        imageView.pinToAllEdges(of: view)

        downloadImage(from: landscapeURLString, from: defaultSession)
    }
    private func downloadImage(from urlString: String, from session : URLSession){
        guard let url = URL(string: urlString) else{
            fatalError("bad String we got!")
        }

        let urlRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 15)
        print("url.hashValue: \(urlRequest.hashValue)")

        let task = session.dataTask(with: urlRequest) { [weak self] (data, response, error) in

            guard error == nil else {
                print(error)
                return
            }
            guard let httpResponse = response as? HTTPURLResponse,
                (200...299).contains(httpResponse.statusCode) else {
                    print("response NOT 2xx: \(response)")
                    return
            }

            for header in httpResponse.allHeaderFields{
                if let key = header.key as? String, key == "Cache-Control"{
                    print("found Cache-Control: \(httpResponse.allHeaderFields["Cache-Control"])")
                }
            }

            if let data = data,
                let image = UIImage(data: data){
                let lizardURL = URL(string: self!.lizardURLString)
                let lizardURLRequest = URLRequest(url: lizardURL!)

                let landscapeCachedURLPResponse : CachedURLResponse = CachedURLResponse(response: response!, data: data, userInfo:nil, storagePolicy: .allowed)
                print("before storing into cache: \(String(describing: session.configuration.urlCache?.cachedResponse(for: lizardURLRequest)))")

                session.configuration.urlCache?.storeCachedResponse(landscapeCachedURLPResponse, for: lizardURLRequest)    

                print("after storing into cache: \(String(describing: session.configuration.urlCache?.cachedResponse(for: lizardURLRequest)))")
                print("lizardRequest.hashValue: \(lizardURLRequest.hashValue)")

                DispatchQueue.main.async {
                    self?.imageView.image = image
                }
            }
        }
        task.resume()
    }        
}


extension UIView{

    func pinToAllEdges(of view: UIView){
        let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
        let top = topAnchor.constraint(equalTo: view.topAnchor)
        let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
        let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)

        NSLayoutConstraint.activate([leading, top, trailing, bottom])
    }
}

我已经验证过的事情:
  • 我的landscapeURLString有一个cache-control header ,其中max-age31536000
  • 如果是全新安装,则在存储到缓存中之前,我的lizardURLString的cachedResponsenil。但是存储后,不再是nil。结果,我得出结论,我已经成功地将某些内容存储到缓存中了!
  • 我也怀疑URLCache将URLRequest视为键。所以我打印了lizardURLString的hashValue。它与我存储的 key 相同。结合以上几点,我得出结论,确切的 key 存在于缓存中!
  • 我还可以看到,当我将其存储在缓存中时,currentMemoryUsage会增加。

  • 我如何进行测试以及看到的内容:
  • 我刚刚下载了风景图片。
  • 关闭我的互联网
  • 单击按钮以下载蜥蜴图像。

  • 显然,它是离线的。我希望它可以从缓存中使用,但事实并非如此。我得到的只是一个超时!

    我也尝试将cachePolicy更改为returnCacheDataElseLoad,但这也无济于事

    编辑1:

    我还尝试着做大卫说过的话:
    let landscapeHTTPResponse : HTTPURLResponse = HTTPURLResponse(url: self!.lizardURL, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: (httpResponse.allHeaderFields as! [String : String]))!
    let landscapedCachedURLPResponse : CachedURLResponse = CachedURLResponse(response: landscapeHTTPResponse, data: data, userInfo:nil, storagePolicy: .allowed)
    

    并将存储的landscapedCachedURLPResponse放入缓存中。那也不起作用。它也会超时-并非每次都进入缓存。

    编辑2:

    所以我取得了一些进展。或者,也许退后一步,又向前迈出了一步。

    我试图查看是否可以为相同的 URL存储响应,并查看清空缓存后是否可以检索响应。我没能力

    我正在创建这样的缓存响应:
    let cachedResponse = CachedURLResponse(response: response!, data: data, userInfo:nil, storagePolicy: .allowed)
    

    或像这样:
    let cachedResponse = CachedURLResponse(response: response!, data: data)
    

    是什么使这部分起作用?:
    let cachedResponseFromCache = session.configuration.urlCache?.cachedResponse(for: self!.landscapeURLRequest)
    self._cachedResponse = cachedResponseFromCache
    

    然后我:
  • 刷新了缓存
  • 关闭了互联网
  • 尝试下载图像,但没有成功,这很好。这是预期的行为
  • cachedResponseFromCache属性存储到缓存中。
  • 能够从缓存中检索!

  • 我不确定从缓存本身退出与从Response + Data创建缓存之间有什么区别。

    这很重要,因为我开始质疑是否还有某种形式的internal bugs in URLCache。这使我有理由相信它可能会按预期工作。

    现在,我知道将数据存储到缓存的过程了。我知道我的URLResponse很好。我只需要通过映射URLRequest来工作

    EDIT3:

    Guy Kogus建议,我的URL必须来自同一来源。
    因此,一旦我下载了他提到的bearImage,我的lizardImage就会通过。瞧!

    作为非常重要的调试说明,我了解到:即使您在问题的某个部分(它本身正在缓存风景图像)上获得成功,更改变量(此处更改初始URL)也总是可以更改整个测试结果。

    他怀疑这是因为 header 中的Server已共享,这对于查找cachedResponse很重要。

    我通过说我的lizardURLRequest是在在线时发出的来驳斥这一说法的,因此没有什么可比的,但是它可以工作!
    因此,下一个想法是,它可能与URL的某些部分有关,例如它的第一部分或其他内容。

    因此,我去修改了lizardURL的来源:

    https://upload.wikimedia.org/wikipedia/commons/e/e0/Large_Scaled_Forest_Lizard.jpg

    像这样:
    https://skdhfsupload.qwiklkjlkjimedia.com/qwikipehkjdia/eeeeeecommons/sdalfjkdse/aldskfjae0/extraParam/anotherextraparam/asasdLarge_Scaled_Forest_Lizard.jpeg

    我在网址中添加了哑字符。我还添加了额外的分割。我在最后更改了文件类型。

    仍然可以正常工作。因此,我唯一可以得出的结论是, header 中的某项内容正在做出决策。

    我的landscapeURL的 header 是:(为此无法缓存另一个URL)
    Content-Length : 997361
    x-cache : HIT, MISS
    cf-ray : 472793e93ce39574-IAD
    x-served-by : cache-lax8621-LAX, cache-iad2132-IAD
    cf-cache-status : HIT
    Last-Modified : Sun, 14 Oct 2018 2:10:05 GMT
    Accept-Ranges : bytes
    Vary : Accept-Encoding
    x-content-type-options : nosniff
    Content-Type : image/jpeg
    expect-ct : max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    Set-Cookie : __cfduid=d5f5fd59ce5ff9ac86e42f8c008708ae61541004176; expires=Thu, 31-Oct-19 16:42:56 GMT; path=/; domain=.pexels.com; HttpOnly
    Expires : Thu, 31 Oct 2019 16:42:56 GMT
    Server : cloudflare
    Cache-Control : public, max-age=31536000
    Date : Wed, 31 Oct 2018 16:42:56 GMT
    

    我的BearURL的 header 是:(为此缓存另一个URL是可行的)
    Date : Wed, 31 Oct 2018 16:46:38 GMT
    Content-Length : 215104
    x-client-ip : 2001:558:1400:4e:808c:2738:43e:36f5
    access-control-expose-headers : Age, Date, Content-Length, Content-Range, X-Content-Duration, X-Cache, X-Varnish
    x-cache : cp1076 miss, cp1088 hit/21
    Age : 27646
    Etag : 00e21950bf432476c91b811bb685b6af
    Strict-Transport-Security : max-age=106384710; includeSubDomains; preload
    x-analytics : https=1;nocookies=1
    Accept-Ranges : bytes
    x-object-meta-sha1base36 : 42tq5grg9rq1ydmqd4z5hmmqj6h2309
    x-varnish : 48388488, 503119619 458396839
    x-cache-status : hit-front
    Content-Type : image/jpeg
    x-trans-id : tx08ed43bbcc1946269a9a3-005bd97070
    Last-Modified : Fri, 04 Oct 2013 23:30:08 GMT
    Access-Control-Allow-Origin : *
    timing-allow-origin : *
    x-timestamp : 1380929407.39127
    Via : 1.1 varnish (Varnish/5.1), 1.1 varnish (Varnish/5.1)
    

    重要的提示:

    对于BearURL,可以缓存BearURL lizardURL或任何其他URL。
    对于landscapeURL,缓存仅适用于landscapeURL本身。它不适用于任何其他URL。

    因此,当前的问题状态是:需要包含哪些 header 才能使其正常工作?

    最佳答案

    欢迎来到异步缓存的美好世界。 NSURLCache是​​高度异步的。仅仅因为您将数据塞入其中并不意味着它可用于检索。您必须先让主运行循环返回,然后才能使用它,甚至可能要稍等片刻。在存储响应后立即返回响应的失败并非罕见。尝试在大约五秒钟后分派(dispatch)它。

    其次,您的缓存可能有点小,无法存储多兆字节的图像。尝试将其增大,看看是否有帮助。

    最后,当您说“关闭互联网”时,您的意思是什么?您说您正在超时。通常,如果您在禁用所有连接的情况下将设备置于“飞行”模式,则在出现故障(指示无连接)之前,设备不应坐在那里过多的时间。如果这没有发生,则正在发生奇怪的事情,几乎就像在 session 上设置了waitsForConnectivity一样。 (您不是在后台发出网络请求,是吗?如果是,请尝试将waitsForConnectivity显式设置为NO,这样它们就不会等待连接可用。)

    同样,对于这种用法,您可能必须删除Vary:Accept-Encoding header 或提供一致的用户代理字符串。该头导致高速缓存基本上是每个浏览器。这可能会导致缓存以意外的方式运行,并且可能是您所看到的怪异现象的原因。

    请注意,剥离Vary header 有点麻烦,并且可能不是解决此问题的最正确方法。理想情况下,您应该调整必须调整的所有传出 header 字段,以便即使存在该 header 也可以使用。但是您必须对其进行研究,并弄清楚需要哪些领域,因为我不知道。 :-)

    关于ios - 使用storeCachedResponse存储在缓存中后,未检索到URLresponse,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52938033/

    相关文章:

    ios - UITextField shouldChangeCharactersInRange 触发两次?

    ios - 导航栏按钮项目色调

    http - 控制发出 HTTP 请求的推荐方法?

    iphone - 使用 Grand Central Dispatch,我如何检查是否有 block 已经在运行?

    java - 从输入流中解析 multipart/form-data 的库和示例

    .htaccess - https 和 http 组合 .htaccess

    c# - 是 type.isSubclassOf(Type otherType) 缓存还是我必须自己做?

    php - PHP 中的引用计数

    MySQL 过程 while 循环 : Gets stuck after one iteration - Cache Clean up

    ios - 如何从单独的 View Controller 添加到数组