本文介绍了使用storeCachedResponse存储在缓存中后,未检索到URLresponse的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to inject data/response from URLRequest into another URLRequest in my cache.

这只是示例代码.准备将其转储到项目中.

This is just a sample code. It's ready to be dumped into a project.

我想做的是使用从我的landscapeURLString网络请求中检索到的响应+数据...存储到我的lizardURLString 请求的会话缓存中.

What I'm trying to do is use the response + data retrieved from my landscapeURLString network request...store into my session's cache for my lizardURLString request.

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标头,而max-age31536000
  • 如果是全新安装,则之前存储到缓存中,我的lizardURLString的cachedResponsenil.但是 存储后,它不再是nil.结果,我得出结论,我已经成功地将某些东西存储到缓存中了!
  • 我还怀疑URLCache将URLRequest视为键.所以我打印了lizardURLString的hashValue.它与我存储的密钥相同.结合以上几点,我得出结论,确切的密钥存在于缓存中!
  • 我还可以看到,将其存储在缓存中时,currentMemoryUsage会增加.
  • Things I've already validated:

    • My landscapeURLString has a cache-control header with a max-age of 31536000
    • If it's a fresh install, then before storing into the cache, my cachedResponse for lizardURLString is nil. But after storing, it's no longer nil. As a result I conclude that I'm successfully storing something into the cache!
    • I also suspect the URLCache considers the URLRequest as the key. So I printed the hashValue of my lizardURLString. It's same as the key I've stored. Combining that with the point above, I concluded that exact key exists in cache!
    • I can also see that when I store it in my cache, my currentMemoryUsage increases.
      1. 我只是下载风景图片.
      2. 关闭我的互联网
      3. 单击按钮以下载蜥蜴图像.

      很明显,它处于离线状态.我希望它可以从缓存中使用,但事实并非如此.我得到的只是一个超时!

      Obviously it's offline. I expect it to use from the cache but it doesn't. All I get is a time out!

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

      I also tried changing the cachePolicy to returnCacheDataElseLoad, but that didn't help either

      我也尝试着做大卫说过的话:

      I also tried doing what David said and do:

      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放入缓存.那也不起作用.它也会超时-并非每次都进入缓存.

      and the stored landscapedCachedURLPResponse into the cache. That didn't work either. It times out as well — it doesn't every look into the cache.

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

      So I made some progress. Or perhaps took one step back and one step forward.

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

      I tried to see if I can store the response for the same URL and see if I can retrieve the response after I empty my cache. I wasn't able to.

      我正在创建这样的缓存响应:

      I was creating my cached response like this:

      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
      

      然后我:

      1. 清除了缓存
      2. 关闭互联网
      3. 试图下载图像,但没有成功,这是好的.这是预期的行为
      4. cachedResponseFromCache属性存储到缓存中.
      5. 能够从缓存中检索!
      1. flushed the cache
      2. turned off internet
      3. attempted to download image, but had no success which is good. It's the expected behavior
      4. stored cachedResponseFromCache property into the cache.
      5. was able to retrieve from cache!

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

      I'm not sure what's the difference between pulling off from cache itself and creating the cache from Response + Data.

      这很重要,因为我开始质疑是否还有某种形式的 URLCache中的内部错误.这使我有理由相信它可能可以按预期工作.

      This is important because I was starting to question if there are still some form of internal bugs in URLCache. This has given me reason to believe that it may be working as expected.

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

      Now I know the process of storing into cache works. I know my URLResponse is good. I just need to work my way through mapping the URLRequest

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

      Guy Kogus suggested that my URLs need to be from the same source.So once I downloaded the bearImage he mentioned, my lizardImage was coming through. VOILA!

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

      As very important debugging note that I learned: Even if your getting success on some part (that it was caching the landscape image for itself) of the problem, changing variables (here changing the initial URL) can always change the entire testing results.

      他怀疑这是因为标题中的Server处于共享状态,这对于查找cachedResponse很重要.

      He suspected that it was because the Server in header in shared and that's important for looking up the cachedResponse.

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

      I refuted that claim by saying that my lizardURLRequest is made when it's online so there's nothing for it compare with, yet it works!So the next idea was that it may have something to do with some part of the URL, like it's first segment or something.

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

      So then I went and altered the lizardURL from:

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

      类似于:

      to something like: https://skdhfsupload.qwiklkjlkjimedia.com/qwikipehkjdia/eeeeeecommons/sdalfjkdse/aldskfjae0/extraParam/anotherextraparam/asasdLarge_Scaled_Forest_Lizard.jpeg

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

      I added dumb characters in the URL. I also added extra segments into it. I changed the file type at the end.

      仍然有效.因此,我唯一可以得出的结论是,标头中的某项内容正在做出决策.

      Still it was working. So the only thing I can conclude is that something from the Headers is doing the decision making.

      我的 landscapeURL 的标题是:(为此无法缓存另一个URL)

      The headers for my landscapeURL are: (caching for another URL doesn't work for this)

      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 是:(为此可以缓存另一个URL)

      The headers for my BearURL are: (caching for another URL works for this)

      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本身.它不适用于任何 other URL.

      Important note:

      For the BearURL, caching for the BearURL and lizardURL or any other URL works.For landscapeURL, caching only works for the landscapeURL itself. It doesn't work for any other URL.

      推荐答案

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

      Welcome to the wonderful world of asynchronous caches. NSURLCache is highly asynchronous. Just because you've shoved data into it doesn't mean it is available for retrieval. You have to let the main run loop return before it will be available, and possibly even wait a little while. The failure to return a response immediately after storing it is not at all unusual. Try dispatching it after five seconds or so.

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

      Second, your cache might be a bit on the small size for storing multi-megabyte images. Try bumping that up and see if it helps.

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

      Finally, what do you mean when you say that you "turn off your Internet?" You say that you're getting a timeout. Normally, if you put the device into Airplane mode with all connectivity disabled, it should not sit there for any significant amount of time before failing with an error indicating no connectivity). If that isn't happening, something strange is happening, almost as if waitsForConnectivity is getting set on the session or something. (You aren't making the network requests in the background, are you? If so, try explicitly setting waitsForConnectivity to NO so that they won't wait for a connection to be available.)

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

      Also, for this usage, you may have to strip out the Vary: Accept-Encoding header or provide a consistent user agent string. That header causes the cache to basically be per-browser. This may cause the cache to behave in unexpected ways, and is probably the cause of the weirdness you're seeing.

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

      Note that stripping out the Vary header is a bit of a hack, and probably isn't the most correct way to fix the issue; ideally, you should tweak whatever outgoing header fields you have to tweak so that it works even with that header present. But you'd have to research it and figure out exactly what fields are needed, because I have no idea. :-)

      这篇关于使用storeCachedResponse存储在缓存中后,未检索到URLresponse的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-05 23:03