SDWebImageSwiftUI icon indicating copy to clipboard operation
SDWebImageSwiftUI copied to clipboard

Is it possible to preserve images in cache even in case of a HTTP 300/400/500 response from server?

Open adams-family opened this issue 1 year ago • 11 comments

I'm using SDWebImage in order to preserve data bandwidth with .refreshCached in order to update images from time to time:

WebImage(url: url, options: .refreshCached, options: .refreshCached)

This works great even if the network is down, images are served from cache.

However, if the URL of the image returns 300/400/500, etc... - which may happen - SDWebImage erased the image from cache and no image is displayed. This is not a desirable behaviour as 1) servers can be down temporarily for maintenance, 2) public WiFi networks return 302 Moved for unauthenticated users, etc - I would prefer keeping images in cache until a new valid image is downloaded.

Is that possible?

adams-family avatar Aug 11 '22 11:08 adams-family

Why not use SDWebImageDownloadConfig.acceptableStatusCodes ?

https://sdwebimage.github.io/documentation/sdwebimage/sdwebimagedownloaderconfig/acceptablestatuscodes

You can read documentation firstly and search for the result

dreampiggy avatar Aug 12 '22 04:08 dreampiggy

When you config acceptableStatusCodes to nil, behaves like below:

  1. When server down and return error HTTP response, although we don't mark URLSession fail and continue to run
  2. Because of the returned HTTP body data, can not decode to any image format, then the DownloadOperation mark as failed with error code .badimagedata
  3. From top level, sd_setImage API get the NSError and you can do logic on it

dreampiggy avatar Aug 12 '22 04:08 dreampiggy

@dreampiggy Thanks for your response. Interestingly, although I added the following code to my app:

import SDWebImage
import SDWebImageSwiftUI

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        SDWebImageDownloader.shared.config.acceptableStatusCodes = [200]
        
        return true
    }
}

Still an HTTP 403 status on a resource causes this image to show up from cache on load, but disappear in about 5 seconds (I guess when the HTTP call is unsuccessful after a few attempts):

struct ContentView: View {
    var body: some View {
        WebImage(url: URL(string: "<any_http_403_resource>")!, options: .refreshCached)
            .resizable()
    }
}

Any ideas what is wrong with my setup? Thanks!

adams-family avatar Aug 12 '22 09:08 adams-family

This is NSIndexSet. Your "[200]" menas only HTTP 200 will treat as success.

I menas you provide a IndexSet with range like [200, 600) or something

dreampiggy avatar Aug 12 '22 11:08 dreampiggy

Maybe you misunderstand that refreshCache means.

That strange option behave:

  1. First read image from cache and callback to set image on View
  2. When network finished, check whether the download image is equal to previous callback image
  3. If same, do nothing and no second callback
  4. If not same, callback second with the new image (note new image will be nil if download failed)

dreampiggy avatar Aug 12 '22 11:08 dreampiggy

Maybe you misunderstand that refreshCache means.

That strange option behave:

  1. First read image from cache and callback to set image on View
  2. When network finished, check whether the download image is equal to previous callback image (we use context option dict to keep a strong reference to that previous image🥲 see hack:https://sdwebimage.github.io/documentation/sdwebimage/sdwebimagecontextoption/loadercachedimage )
  3. If same, do nothing and no second callback
  4. If not same, callback second with the new image (note new image will be nil if download failed)

dreampiggy avatar Aug 12 '22 11:08 dreampiggy

For you case, you can either:

  1. Use another feature called .avoidAutoSetImage and manual set image and filter this second callback
  2. Use responseModifier API, to read the previous image from cache again (ugly hack) and return, so the new image and previous inage will be the same and no second callback triggered (...)
  3. Feature request: Hack that context option (.loaderCachedImage) which keep the previous image, set it to nil when network download failed. Or just write another wrapper SDImageDownloader. Pseudocode
class MyImageLoader : SDImageLoaderProtocol {
  func loadImage(with: url, callback) {
    let loadedImage = context[.loaderCachedImage]
    SDImageDownloader.shared.loadImage(with: url) { image, data, error in 
      if error && loadedImage {
          callback(loadedImage, nil, NSError(domain: SDWebImageErrorDomain code: .cacheNotModified))
          return
      }
      callback(image, data, error)
    }
  }
}

Is this feature really useful in general ?

dreampiggy avatar Aug 12 '22 11:08 dreampiggy

I think that in general it would be useful to have a built-in option to keep cached images as long as there is a valid new version of them. If the remote server containing the images is broken for whatever reason (developer mistake, hardware issue) and returns invalid response codes such as 403, 404, 500, ... the user of the mobile app would still see the correct image.

adams-family avatar Aug 16 '22 08:08 adams-family

Seems a feature request. Maybe we can create a issue in https://github.com/SDWebImage/SDWebImage/issues and reference this.

It's not hard to implements, but need a new options like SDWebImageRefreshCachedIgnoreError, or another global config to avoid huge codebase changes ?

dreampiggy avatar Aug 16 '22 09:08 dreampiggy

I like the .refreshCachedIgnoreError option. Should we let people vote before I create the new request?

adams-family avatar Aug 16 '22 10:08 adams-family

It's OK to fire issue first. The detail API name actually we can talk in PR.

dreampiggy avatar Aug 16 '22 10:08 dreampiggy