簡體   English   中英

使用storeCachedResponse存儲在緩存中后,未檢索到URLresponse

[英]URLresponse is not retrieved after storing in cache using storeCachedResponse

目標

我試圖將數據/響應從URLRequest注入到緩存中的另一個 URLRequest中。

設定

這只是一個示例代碼。 准備將其轉儲到項目中。

我想做的是使用從我的landscapeURLString網絡請求中檢索到的響應和數據...存儲到我的會話緩存中的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標頭, max-age 3153600031536000
  • 如果它是一個新的安裝,然后存儲到緩存之前 ,我cachedResponse為lizardURLString是nil 但是存儲之后 ,它不再是nil 結果,我得出結論,我已成功將某些內容存儲到緩存中!
  • 我也懷疑URLCache將URLRequest視為鍵。 所以我打印了lizardURLString的lizardURLString 它與我存儲的密鑰相同。 結合以上幾點,我得出結論,確切的密鑰存在於緩存中!
  • 我還可以看到,將其存儲在緩存中時, currentMemoryUsage增加。

我如何進行測試以及看到的內容:

  1. 我只是下載風景圖片。
  2. 關閉我的網際網路
  3. 單擊按鈕下載蜥蜴圖像。

顯然,它是離線的。 我希望它可以從緩存中使用,但事實並非如此。 我得到的只是一個超時!

我還嘗試將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

然后我:

  1. 刷新緩存
  2. 關閉互聯網
  3. 嘗試下載圖像,但沒有成功,這很好。 這是預期的行為
  4. cachedResponseFromCache屬性存儲到緩存中。
  5. 能夠從緩存中檢索!

我不確定從緩存本身拉出和從Response + Data創建緩存之間有什么區別。

這很重要,因為我開始質疑URLCache中是否仍然存在某種形式的內部錯誤 這使我有理由相信它可能會按預期工作。

現在,我知道將數據存儲到緩存的過程了。 我知道我的URLResponse很好。 我只需要通過映射URLRequest來工作

編輯3:

Guy Kogus建議我的URL必須來自同一來源。 因此,一旦我下載了他提到的bearImage,我的lizardImage就會通過。 瞧!

作為非常重要的調試說明,我了解到:即使您在問題的某個部分(它本身正在緩存風景圖像)上獲得成功,更改變量(此處更改初始URL)也總是可以更改整個測試結果。

他懷疑這是因為共享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

我在網址中添加了啞字符。 我還添加了額外的細分。 我在最后更改了文件類型。

它仍然在工作。 因此,我唯一可以得出的結論是,標頭中的某項內容正在做出決策。

我的landscapeURL的標頭是:(為此緩存另一個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的標頭是:( 為此緩存另一個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。

因此,當前的問題狀態是:要使其正常工作,需要包含哪些標頭

歡迎來到異步緩存的美好世界。 NSURLCache是​​高度異步的。 僅僅因為您將數據塞入其中並不意味着它可用於檢索。 您必須先讓主運行循環返回,然后才能使用它,甚至可能要稍等片刻。 在存儲響應后立即返回響應的失敗並非罕見。 大約五秒鍾后嘗試調度它。

其次,您的緩存可能有點小,無法存儲數兆字節的圖像。 嘗試將其增大,看看是否有幫助。

最后,當您說“關閉互聯網”是什么意思? 您說您超時了。 通常,如果您在禁用所有連接的情況下將設備置於“飛行”模式,則在出現故障(指示沒有連接)之前,設備不應坐在那里過多時間。 如果這沒有發生,則正在發生奇怪的事情,幾乎就像在會話上設置了waitsForConnectivity一樣。 (您不是在后台發出網絡請求,是嗎?如果是,請嘗試將waitsForConnectivity顯式設置為NO,這樣它們就不會等待連接可用。)

同樣,對於這種用法,您可能必須去除Vary:Accept-Encoding標頭或提供一致的用戶代理字符串。 該頭導致高速緩存基本上是每個瀏覽器。 這可能會導致緩存以意外的方式運行,並且可能是您所看到的怪異現象的原因。

請注意,剝離Vary標頭有點麻煩,並且可能不是解決此問題的最正確方法。 理想情況下,您應該調整必須調整的所有傳出標頭字段,以便即使存在該標頭也可以使用。 但是您必須對其進行研究,並弄清楚需要哪些領域,因為我不知道。 :-)

這不是一個完整的答案,但是它應該使您朝正確的方向發展。

問題與您的代碼無關,我相信這基本上沒問題。 問題在於您從landscapeURLString得到的響應,因為圖像存儲在Cloudflare中。 如果您使用同一來源的2張圖片(例如,嘗試使用Wikipedia上的那只而不是images.pexels.com上的圖片),則該圖片應該可以使用。

我嘗試打印出下載images.pexels.com圖片的響應和標題,這就是我看到的內容:

response: <NSHTTPURLResponse: 0x600002bf65c0> { URL: https://upload.wikimedia.org/wikipedia/commons/e/e0/Large_Scaled_Forest_Lizard.jpg } { Status Code: 200, Headers {
    "Accept-Ranges" =     (
        bytes
    );
    "Cache-Control" =     (
        "public, max-age=31536000"
    );
    "Content-Length" =     (
        997361
    );
    "Content-Type" =     (
        "image/jpeg"
    );
    Date =     (
        "Wed, 31 Oct 2018 11:38:52 GMT"
    );
    Expires =     (
        "Thu, 31 Oct 2019 11:38:52 GMT"
    );
    "Last-Modified" =     (
        "Fri, 26 Oct 2018 6:31:56 GMT"
    );
    Server =     (
        cloudflare
    );
    Vary =     (
        "Accept-Encoding"
    );
    "cf-cache-status" =     (
        HIT
    );
    "cf-ray" =     (
        "4725d67b0ae461bd-BCN"
    );
    "expect-ct" =     (
        "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\""
    );
    "x-cache" =     (
        "HIT, MISS"
    );
    "x-content-type-options" =     (
        nosniff
    );
    "x-served-by" =     (
        "cache-lax8643-LAX, cache-mad9437-MAD"
    );
} }
headers: ["Accept-Ranges": "bytes", "Content-Type": "image/jpeg", "Last-Modified": "Fri, 26 Oct 2018 6:31:56 GMT", "Vary": "Accept-Encoding", "cf-ray": "4725d67b0ae461bd-BCN", "Date": "Wed, 31 Oct 2018 11:38:52 GMT", "Server": "cloudflare", "Expires": "Thu, 31 Oct 2019 11:38:52 GMT", "x-content-type-options": "nosniff", "expect-ct": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"", "x-cache": "HIT, MISS", "x-served-by": "cache-lax8643-LAX, cache-mad9437-MAD", "cf-cache-status": "HIT", "Content-Length": "997361", "Cache-Control": "public, max-age=31536000"]

那里可能存在某種嘗試將請求URL與導致緩存未命中的響應字段進行匹配的方法,但是我對此卻知之甚少。 其他人可能會為您找到它(因此,為什么我說這個答案不完整)。

我通過用以下命令替換dataTaskcompleteHandler中的第一個警衛來解決此問題:

guard error == nil else {
  print(error)

  if let cr = session.configuration.urlCache?.cachedResponse(for: urlRequest){
     let image = UIImage(data: cr.data)
     DispatchQueue.main.async {
       self?.imageView.image = image
     }
   }

   return
}

如果請求失敗,它將對該請求采取緩存的響應

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM