6

Goal

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

Setup

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

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])
    }
}

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.

How I'm testing and what I'm seeing:

  1. I just download the landscape image.
  2. Turn off my internet
  3. Click the button to download the lizard image.

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

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

EDIT1:

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)

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.

EDIT2:

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

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)

or just like this:

let cachedResponse = CachedURLResponse(response: response!, data: data)

What got this part to work?:

let cachedResponseFromCache = session.configuration.urlCache?.cachedResponse(for: self!.landscapeURLRequest)
self._cachedResponse = cachedResponseFromCache

Then I:

  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!

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

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.

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

EDIT3:

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!

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.

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

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.

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.

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

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)

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.

So the current state of the question is: What headers need to be included for this to work?

mfaani
  • 33,269
  • 19
  • 164
  • 293
  • **Bounty** was meant to be offered to dgatwood's answer. But due to some mistake/delay by me, it wasn't. So treat his answer as the most important answer. Though Guy's note on headers was vital in resolving the issue. FWIW I offered bounty on a different answer of his :) – mfaani Nov 06 '18 at 03:25

3 Answers3

3

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.

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.)

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.

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. :-)

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • Thank you so much. 1) I waited for more than 5 seconds. 2) My image is 1mb. My cache size is 500mb! 3) What is the expected result to you? That it should fail or that it should read from cache? Because clearly now it fails as if it's not reading from cache. What do you mean by background? I'm trying to retrieve the cache when app is in foreground. Obviously the network request is to be performed in the background... David, whenever you have time can you copy my code and give it a try? The code is complete to reproduce my issue. Has all the links and everything... – mfaani Oct 23 '18 at 15:14
  • FWIW if I 1. download the landscape image. 2. kill the app 3. turn off the internet for the device and load the **landscape** image then it loads from cache immediately. It just doesn't load for when I force inject it into the cache for my **lizard** request... – mfaani Oct 23 '18 at 15:15
  • I edited my cache size. In my tests it was ran with 500mb. And my image size is 1mb so it follows Apple's guideline for being less than 5% of cache size. – mfaani Oct 23 '18 at 21:14
  • Have you tried creating a *new* `NSHTTPURLResponse` object that A. isn't already in the cache, and B. was created based on the lizard URL, rather than a different URL? – dgatwood Oct 24 '18 at 05:34
  • I don't understand your suggestion. _was created **based** on the lizard URL_ The lizardURL never reaches the server! Hence I can't create a response based off of that. – mfaani Oct 24 '18 at 15:07
  • Allocate a new HTTPURLResponse object. When you do this, the first parameter is the URL that the response is valid for; pass the lizard URL. Then copy all of the other properties from the existing received response. (For the HTTP version value, just assume HTTP/1.1, because that particular value isn't exposed as a property, and thus you can't copy it; IIRC, it also isn't used anywhere in the underlying NSURL/CFNetwork code, so it doesn't matter if that value is wrong anyway.) – dgatwood Oct 24 '18 at 20:09
  • That was a good suggestion. I tried. Didn't work. See my edit. Maybe I'm just doing something wrong. I've added a bounty. If you had time I'd appreciate if you can paste my code and give it a try. – mfaani Oct 25 '18 at 22:15
  • Try one more thing: do a dispatch_after and check to see if, five seconds later, querying the cache explicitly for that request returns the correct results. That will narrow down what isn't working. BTW, I'm assuming that you aren't tearing down the view controller that's retaining your custom cache, right? – dgatwood Oct 25 '18 at 22:24
  • _querying the cache explicitly for that request returns the correct results_ it does return a result. I tried doing `if let data = defaultSession.configuration.urlCache!.cachedResponse(for: lizardURLRequest)?.data { DispatchQueue.main.async { [weak self] self?.imageView.image = UIImage(data: data) } }` and I did get landscape image for my lizardRequest **when I directly queried against the cache**. If I make a request normal request, it just says your offline and that's it. – mfaani Oct 25 '18 at 22:53
  • Thank you for your comments. A) FWIW before I was using network conditioner with 100% loss and my queries were timing out. Now I'm just tuning internet off. And it just then immediately tells me that my internet is off. Though again for the landscape image itself it works fine. It immediately returns result from cache. B) And by tearing down I assume you're asking if I'm staying on the screen...or not popping the viewcontroller. No I'm not tearing it down. I just stay on the screen. – mfaani Oct 25 '18 at 22:56
  • just a side note. Is URLSession smart enough to invalidate the cachedResponse of a GET made against `trip/123` _after_ you've made a POST request for `trip/123`. Because that would at least resolve 50% of the problem which is getting rid of invalidated cache. Still I'd have to make a new network request to get the new value. My question here is all about how to avoid that extra call. – mfaani Oct 25 '18 at 22:59
  • Can you clarify a little more on what you want to do. I find the "GOAL" a little confusing. I'm just a little more interested in the purpose of why you want to inject the part of the response from one response into the request of another? Why data do you want to re-use? BTW, I took a cursory look and it didn't look like to me you were doing anything with the second request. – Mobile Ben Oct 26 '18 at 15:58
  • @MobileBen please comment on the question itself. Also please read the bounty description I've added and [this comment](https://stackoverflow.com/questions/52938033/urlresponse-is-not-retrieved-after-storing-in-cache-using-storecachedresponse#comment92903074_52942221) – mfaani Oct 26 '18 at 22:39
  • The other 2 answers have also been very helpful. But not conclusive. FYI I made a 3rd edit to the question – mfaani Oct 31 '18 at 17:35
  • Remove the Vary header. – dgatwood Oct 31 '18 at 21:34
  • David **that works:** `var headers = (httpResponse.allHeaderFields as! [String : String]); headers["Vary"] = nil; let landscapeHTTPResponse : HTTPURLResponse = HTTPURLResponse(url: self!.WhateverURL, statusCode: 200, httpVersion: "1.1", headerFields: headers)!; self?._cachedResponse = CachedURLResponse(response: landscapeHTTPResponse, data: data, userInfo:nil, storagePolicy: .allowed);` – mfaani Nov 01 '18 at 06:18
  • Any clue on to how the BearURL is caching? It doesn't have a `cache-control` header... – mfaani Nov 01 '18 at 07:16
  • AFAIK, Cache-control is only used when deciding *whether* to store the result in the cache. If you override the protocol policy and send it to the cache anyway, there's nothing at that level of the system to reject it. – dgatwood Nov 01 '18 at 17:13
  • But I'm not overriding it. I'm using `.useProtocolCachePolicy`. Can you also make sure you use `@honey`. otherwise I won't get notified... – mfaani Nov 01 '18 at 17:15
  • Yeah, but that decision happens in the NSURLProtocol class, way back when you were first getting a server response for that the request. Those checks aren't part of the code path that you use when manually adding something into the cache. Or are you saying that the Bear URL is getting automatically cached during the request phase? If so, I think it may be because the existence of a Vary header implies that the response is cacheable. – dgatwood Nov 01 '18 at 17:18
  • _Or are you saying that the Bear URL is getting automatically cached during the request phase_ YES!! The [bearURL](https://upload.wikimedia.org/wikipedia/commons/7/79/2010-brown-bear.jpg) doesn't have a `Vary` header, nor it has a `cache-control` header. Yet its getting cached! I've included the BearURL's headers in my question. – mfaani Nov 01 '18 at 17:29
  • Apparently, the default, if no Cache-control directive is specified, is Private, which means caches like this one treat the resource as cacheable, but proxies can't share the cache between multiple clients. So this is expected. – dgatwood Nov 01 '18 at 18:39
  • Super thanks for your patience and the many comments you've answered. I learned a lot from this question. I believe this is the last question I'll be asking: Without a `max-age` would it cache it indefinitely on the iOS? And when you say private, you mean like my ISP or the CDNs won't cache this? – mfaani Nov 01 '18 at 21:02
  • David, my apologies. I made a mistake by not awarding the bounty. I left it here for more exposure for the community and myself to get more points back from the question. My assumption was that by accepting your answer it will automatically get offered to you. Apparently that's not the case. It only applies to answers written **after** the bounty was offered. I've already flagged this for a moderator. I'll get it resolved – mfaani Nov 03 '18 at 00:45
  • Eh. No worries. :-) – dgatwood Nov 03 '18 at 01:26
  • @dgatwood Is there a reference that NSURLCache is asynchronous? I don't see any mention of async behavior in the documentation. I tried calling `storeCachedResponse(cachedResponse:for:) and removeAllCachedResponses()` and check the result in playground and they appear to take effect immediately. Or am I missing something? – Hlung May 21 '20 at 05:54
  • I wouldn't hold my breath on full documentation of something as esoteric as NSURLCache. They haven't even documented whether UIPasteboard is thread-safe (rdar://28799568). Anyway, I can't say for sure how it works now — only that it historically behaved that way; I wrote this quite a long time ago. Also bear in mind that Swift (particularly in playgrounds) adds layers of complexity that may change its behavior significantly. – dgatwood May 22 '20 at 01:42
  • Is it possible to retrieve the response data object (text/HTML) from a WKWebView.load(URLRequest) like the ones automatically provided by the completion handler of URLSession.shared.dataTask{data, response, error -> Void} or URLSession.shared.downloadTask{location, response, error -> Void}? - I simply need to load the page-HTML-string into a textView for reviewing but I HAVE to use ‘load(URLRequest)’ on WKWebView and not ‘loadHTMLString()’. This is for filing a cookieStore bug to Apple and would give me an additional level of proving my point... – mramosch Mar 28 '21 at 15:05
  • No idea. That's kind of an unrelated question. Try asking it as a question. – dgatwood Mar 29 '21 at 18:50
2

This isn't a complete answer, but it should put you in the right direction.

The issue isn't to do with your code, I believe that it's mostly fine. The issue is regarding the response that you get from the landscapeURLString because the image is stored in Cloudflare. If you use 2 images from the same source, (e.g. try with this bear from wikipedia instead of the image from images.pexels.com) it should work.

I tried printing out the response and headers of downloading the images.pexels.com image and this is what I saw:

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"]

There's probably something in there that is trying to match the request URL with a response field that's causing the cache miss, but I'm not knowledgable enough to know what it is. Somebody else can probably catch it for you (hence why I said that this answer is incomplete).

Guy Kogus
  • 7,251
  • 1
  • 27
  • 32
  • VOILA! Indeed using the bear image made it work. It could also be the first URLSegment of the `URL` or its host. But `Server` seems very reasonable too. What I don't understand is: This bear image's `cache-control` is set to `max-age: 0`. Yet If I call `session.configuration.urlCache?.cachedResponse(for: self!.bearURLRequest)` it **doesn't** come as `nil`. I also tried *force-quiting* the app and downloading the bearImage when I'm offline. Surprisingly it worked. So I printed all the headers and found "max-age" coming under the `Strict-Transport-Security` key. Does that key control caching? – mfaani Oct 31 '18 at 14:24
  • And can I ask _how_ you know of this issue? Did you ever run into a similar issue? – mfaani Oct 31 '18 at 14:40
  • 2nd thought. I'm almost certain it's based on URLPath. Because my lizardRequest NEVER reaches the internet ie it never gets any `Server` header to compare with. When it's offline the only thing that exists for it to look into is the `URLRequest` itself. And the only thing that is different now is the URLPath. I've made everything else the same. Does that make sense? – mfaani Oct 31 '18 at 15:07
  • I just played around and experimented until I realised that the responses and headers were very different. One useful thing to do was, instead of using `session.dataTask(with:completionHandler)` I used just `session.dataTask(with:)` and checked the `URLSessionDataDelegate` callbacks to dig deeper. – Guy Kogus Oct 31 '18 at 17:02
  • It seems that it has nothing to do with the URLPath either. It has something to do with the headers. I've edited the question. Can you take another look? (Saw your comment after I edited the question...) – mfaani Oct 31 '18 at 17:36
  • Try nuking all the headers except Expires and Last-Modified (and ETag, if the server sends it), and set Date to the current date. See if that works. – dgatwood Oct 31 '18 at 20:56
0

I resolved this problem by replacing first guard inside dataTask completionHandler with:

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
}

If request fails, it will take cached response for that request

mfaani
  • 33,269
  • 19
  • 164
  • 293
slobodans
  • 859
  • 1
  • 17
  • 23
  • I mistakenly upvoted this. If I get it correctly your ultimate approach is smart. But your current flow here is incorrect. `if let cr = session.configuration.urlCache?.cachedResponse(for: urlRequest){` should be **before** making any network call. Meaning if it's in the cache then you should just read it from there and not retrieve if you have an error. Still this doesn't explain why the normal process doesn't work. If I'm able to access the cache by calling `cachedResponse` but not directly from making the network call then something is terribly wrong. The behavior should not be different. – mfaani Oct 31 '18 at 15:09
  • Then I misunderstood that getting response from cache should be fallback if network request for image fail. – slobodans Nov 01 '18 at 08:29
  • I can edit. But i'm not sure what do you want me to edit? – slobodans Nov 02 '18 at 07:53