URLresponse не восстанавливается после сохранения в кеше с помощью storeCachedResponse - PullRequest
0 голосов
/ 23 октября 2018

Цель

Я пытаюсь вставить данные / ответ из 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 из 31536000
  • Если - это новая установка, то перед сохранением в кеш, мой cachedResponse для lizardURLString равен nil.Но после хранения больше не nil.В результате я делаю вывод, что я успешно что-то храню в кеше!
  • Я также подозреваю, что URLCache рассматривает URLRequest в качестве ключа.Поэтому я напечатал hashValue моего lizardURLString.Это то же самое, что ключ, который я сохранил.Комбинируя это с вышеприведенным пунктом, я пришел к выводу, что в кеше существует точный ключ!
  • Я также вижу, что когда я сохраняю его в кеше, мой currentMemoryUsage увеличивается.

Как я тестирую и что вижу:

  1. Я просто загружаю изображение ландшафта.
  2. Выключить мой интернет
  3. Нажмите кнопку, чтобы загрузить изображение ящерицы.

Очевидно, что он не в сети.Я ожидаю, что он будет использовать из кэша, но это не так.Все, что я получаю, это время!

Я также пытался изменить cachePolicy на returnCacheDataElseLoad, но это не помогло либо

EDIT1:

Я также попытался сделать то, что сказал и сделал Дэвид:

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 в кэш.Это тоже не сработало.Это также истекает - не все заглядывают в кеш.

EDIT2:

Так что я добился определенного прогресса.Или, возможно, сделал один шаг назад и один шаг вперед.

Я попытался проверить, смогу ли я сохранить ответ для того же 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 .Это дало мне основание полагать, что оно может работать как положено.

Теперь я знаю, как работает процесс сохранения в кеш.Я знаю, что мой URL-ответ хорош.Мне просто нужно пройти через отображение URLRequest

EDIT3:

Гай Когус предположил, что мои URL должны быть из одного источника.Поэтому, как только я скачал упомянутое им изображение медведя, мое изображение ящерицы проходило.VOILA!

Как очень важное замечание по отладке, которое я узнал: даже если вы добились успеха в какой-то части (то, что она кешировала ландшафтное изображение для себя) проблемы, изменение переменных (здесь изменение исходного 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

Я добавил немые символы в URL.Я также добавил дополнительные сегменты в него.Я изменил тип файла в конце.

Все еще это работало.Таким образом, единственное, что я могу сделать вывод, это то, что что-то из Заголовков принимает решения

Заголовки для моего 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.

Итак, текущее состояние вопроса таково: какие заголовки необходимо включить, чтобы это работало?

Ответы [ 3 ]

0 голосов
/ 31 октября 2018

Это не полный ответ, но он должен направить вас в правильном направлении.

Проблема не связана с вашим кодом, я считаю, что это в основном нормально.Проблема касается ответа, который вы получаете от landscapeURLString, потому что изображение хранится в Cloudflare.Если вы используете 2 изображения из одного источника (например, попробуйте использовать этого медведя из википедии вместо изображения из 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-адрес запроса с полем ответа, которое вызывает пропадание кэша, но я не достаточно осведомлен, чтобы знать, что это такое.Кто-то другой, вероятно, может поймать его для вас (поэтому я и сказал, что этот ответ неполон).

0 голосов
/ 31 октября 2018

Я решил эту проблему, заменив первый защитный элемент внутри dataTask completeHandler на:

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
}

Если запрос не выполнен, он примет кешированный ответ на этот запрос

0 голосов
/ 23 октября 2018

Добро пожаловать в удивительный мир асинхронных кешей.NSURLCache очень асинхронный.То, что вы поместили данные в них, не означает, что они доступны для поиска.Вы должны позволить основному циклу выполнения вернуться до того, как он станет доступен, и, возможно, даже немного подождать.Неспособность вернуть ответ сразу после его хранения не является чем-то необычным.Попробуйте отправить его через пять секунд или около того.

Во-вторых, ваш кэш может быть небольшого размера для хранения мультимегабайтных изображений.Попробуйте увеличить это и посмотреть, поможет ли это.

Наконец, что вы имеете в виду, когда говорите, что «выключаете Интернет»?Вы говорите, что получаете тайм-аут.Обычно, если вы переводите устройство в режим «В самолете» с отключенным подключением, оно не должно находиться там в течение значительного периода времени, прежде чем произойдет сбой с ошибкой, указывающей на отсутствие подключения).Если этого не происходит, происходит что-то странное, почти так, как если бы в сеансе устанавливалось значение waitsForConnectivity или что-то в этом роде.(Вы не делаете сетевые запросы в фоновом режиме, не так ли? Если это так, попробуйте явно установить для waitsForConnectivity значение NO, чтобы они не ожидали подключения, доступного.)

Кроме того, для этогоПри использовании вам, возможно, придется удалить заголовок Vary: Accept-Encoding или предоставить согласованную строку пользовательского агента.Этот заголовок приводит к тому, что кеш в основном для каждого браузера.Это может привести к тому, что кеш будет вести себя непредсказуемым образом, и, вероятно, является причиной странности, которую вы видите.

Обратите внимание, что удаление заголовка Vary является чем-то вроде хака, и, вероятно, несамый правильный способ решения проблемы;в идеале вы должны настроить любые исходящие поля заголовка, чтобы они работали даже при наличии этого заголовка.Но вам придется изучить его и выяснить, какие именно поля необходимы, потому что я понятия не имею.: -)

...