SDWebImage / SDWebImageSwiftUI

SwiftUI Image loading and Animation framework powered by SDWebImage
https://sdwebimage.github.io/SDWebImageSwiftUI
MIT License
2.14k stars 219 forks source link

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

Open adams-family opened 2 years ago

adams-family commented 2 years ago

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?

dreampiggy commented 2 years ago

Why not use SDWebImageDownloadConfig.acceptableStatusCodes ?

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

You can read documentation firstly and search for the result

dreampiggy commented 2 years ago

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
adams-family commented 2 years ago

@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!

dreampiggy commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 ?

adams-family commented 2 years ago

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.

dreampiggy commented 2 years ago

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 ?

adams-family commented 2 years ago

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

dreampiggy commented 2 years ago

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