SDWebImage / SDWebImage

Asynchronous image downloader with cache support as a UIImageView category
https://sdwebimage.github.io
MIT License
25.05k stars 5.96k forks source link

[Usage] synchronously check if url(s) exist in memory cache #2757

Closed xzilja closed 5 years ago

xzilja commented 5 years ago

New Issue Checklist

Issue Info

Info Value
Platform Name ios
Platform Version 12.0
SDWebImage Version 5.0.6
Integration Method cocoapods
Xcode Version Xcode 10
Repro rate -
Repro with our demo prj -
Demo project link -

Issue Description and Steps

Using SDWebImage v5 what is the least costly method to check if array of urls exists in memory cache in a synchronous?

While looking through documentation closest thing I found for this use case is cacheKeyForUrl that I believe can be used in a loop to check each url in the array individually and if one of them does not return a key, we can assume that full array is not in the cache.

However, what if I only want to check memory, without hitting disk cache?

Perhaps I am missing something, a helper to check if link(s) are in memory?

dreampiggy commented 5 years ago

@IljaDaderko If you're careful, there are a new series of method inSDImageCache protocol method. containsImageForKey:cacheType:completion. The input cacheType is your desired cache to check, the output cacheType is where we found. (For example you can input .all and we output .disk)

And also, there is the exisiting method in SDImageCache class, the imageFromMemoryCacheForKey:. Because for now, the memory cache only contains the image instance. So actually, the check is equal to the cost of query memory, they're the same design like NSDictionary.objectForKey:.

You can use the demo code to check

SDImageCache *cache;
NSArray<NSString *> *keys;
[keys enumerateObjectsUsingBlock:^(NSString *  _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
    BOOL isInMemoryCache = ([cache imageFromMemoryCacheForKey:key] != nil);
}];

or this, to works for your custom cache protocol in 5.0 (Such as YYCache)

id<SDImageCache> cache;
NSArray<NSString *> *keys;
[keys enumerateObjectsUsingBlock:^(NSString *  _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
    __block SDImageCacheType cacheType;
    [cache containsImageForKey:key cacheType:SDImageCacheTypeMemory completion:^(SDImageCacheType containsCacheType) {
        cacheType = containsCacheType;
    }]
    BOOL isInMemoryCache = cacheType == SDImageCacheTypeMemory;
}];
dreampiggy commented 5 years ago

cacheKeyForUrl is in the manager method. It's used to grab the generated cache key for URL during load. Because not always the URL.absoluteString == cacheKey.

We supports the Cache Key Filter to provide a cutsom key, to help user to provide the high-level fucntion, like the CDN lookup (for example, differnt URL www.1.foo.com/1.jpg, www.2.foo.com/1.jpg is 2 CDN image url, they can match to same cache key, instead of duplicated download and cache).

xzilja commented 5 years ago

Assuming I don't initially know they key for the url (for example I get array of urls from API response) and want to check if all of them are in cache.

I will need to run a loop that combines cacheKeyForUrl and imageFromMemoryCacheForKey for example?

  1. Get the key
  2. Check if it is in memmory
xzilja commented 5 years ago

This far this is my attempt (it's within react-native scope hence RTC and resolve / reject)

RCT_EXPORT_METHOD(checkCache
                  : (NSArray<NSURL *> *)urls resolver
                  : (RCTPromiseResolveBlock)resolve rejecter
                  : (RCTPromiseRejectBlock)reject) {
  __block BOOL cacheExists = YES;

  @try {
    [urls enumerateObjectsUsingBlock:^(NSURL *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
      NSString *key = nil;
      key = [[SDWebImageManager sharedManager] cacheKeyForURL:obj];

      if (key != nil) {
        [[SDImageCache sharedImageCache]
            containsImageForKey:key
                      cacheType:SDImageCacheTypeMemory
                     completion:^(SDImageCacheType containsCacheType) {
                       if (containsCacheType != SDImageCacheTypeMemory) {
                         cacheExists = NO;
                         *stop = YES;
                       }
                     }];
      } else {
        *stop = YES;
      }
    }];
    resolve(@{@"cacheExists" : @(cacheExists)});
  } @catch (NSError *error) {
    reject(@"ASQImage:unknown_error", @"chechCache method failed", error);
  }
}

Did I grasp this concept correctly? At first glance this seems to be working in my app 👍

dreampiggy commented 5 years ago

[SDImageCache sharedImageCache]. If you desired to only use SDImageCache but not other custom cache implementation. You can directly use the imageFromMemoryCacheForKey: API. This don't need a block like that containsImageForKey: protocol method.

Just for-in loop to call this. And collect the result and callback.

But actually, I don't think this is a good React Native Bridge. Consider the user-side of your bridge API.

I can provide a list of url, but you give me a bool value represent the existing. It's ok when I see a true callback. But what to do if I receive a false ? I don't know which URL cache is missing, which is hit.

A better design of this API, should return a hash object. Like this:

[{
   key: "www.foobar.com/1.jpg",
   exist: false
},
{
   key: "www.foobar.com/2.jpg",
   exist: true
},
]

Which may be better. Or if you don't care the bridge callback performance (I remember the React Native briding need JavaScriptCore in another thread), you can receive just one URL and callback whether it exists.

xzilja commented 5 years ago

Ah, I didn't realise url is the key, thought it was something custom. This makes sense. In terms of the bridge perfromance, it's just an async function on js side and within ObjC we only return it one time after the loop (not on every loop run).

I will indeed implement approach that returns array of keys and exist flags like you suggested 👍 Thank you for explaing all of this!