Authored by Bogdan Poplauschi

#625 In order to fix the deadlock, reviewed the `[SDImageCache diskImageExistsWi…

…thKey:]` method. Based on the Apple doc for NSFileManager, using the defaultManager without the dispatch on the ioQueue to avoid the deadlocks. This instance is thread safe. Also created an async variant of this method `[SDImageCache diskImageExistsWithKey:completion:]`
For consistency, added async methods in `SDWebImageManager` `cachedImageExistsForURL:completion:` and `diskImageExistsForURL:completion:`
@@ -26,6 +26,8 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) { @@ -26,6 +26,8 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) {
26 26
27 typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType); 27 typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);
28 28
  29 +typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);
  30 +
29 /** 31 /**
30 * SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed 32 * SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
31 * asynchronous so it doesn’t add unnecessary latency to the UI. 33 * asynchronous so it doesn’t add unnecessary latency to the UI.
@@ -198,7 +200,20 @@ typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType ca @@ -198,7 +200,20 @@ typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType ca
198 - (void)calculateSizeWithCompletionBlock:(void (^)(NSUInteger fileCount, NSUInteger totalSize))completionBlock; 200 - (void)calculateSizeWithCompletionBlock:(void (^)(NSUInteger fileCount, NSUInteger totalSize))completionBlock;
199 201
200 /** 202 /**
201 - * Check if image exists in cache already 203 + * Async check if image exists in disk cache already (does not load the image)
  204 + *
  205 + * @param key the key describing the url
  206 + * @param completionBlock the block to be executed when the check is done.
  207 + * @note the completion block will be always executed on the main queue
  208 + */
  209 +- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
  210 +
  211 +/**
  212 + * Check if image exists in disk cache already (does not load the image)
  213 + *
  214 + * @param key the key describing the url
  215 + *
  216 + * @return YES if an image exists for the given key
202 */ 217 */
203 - (BOOL)diskImageExistsWithKey:(NSString *)key; 218 - (BOOL)diskImageExistsWithKey:(NSString *)key;
204 219
@@ -200,14 +200,26 @@ BOOL ImageDataHasPNGPreffix(NSData *data) { @@ -200,14 +200,26 @@ BOOL ImageDataHasPNGPreffix(NSData *data) {
200 } 200 }
201 201
202 - (BOOL)diskImageExistsWithKey:(NSString *)key { 202 - (BOOL)diskImageExistsWithKey:(NSString *)key {
203 - __block BOOL exists = NO;  
204 - dispatch_sync(_ioQueue, ^{  
205 - exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];  
206 - });  
207 - 203 + BOOL exists = NO;
  204 +
  205 + // this is an exception to access the filemanager on another queue than ioQueue, but we are using the shared instance
  206 + // from apple docs on NSFileManager: The methods of the shared NSFileManager object can be called from multiple threads safely.
  207 + exists = [[NSFileManager defaultManager] fileExistsAtPath:[self defaultCachePathForKey:key]];
  208 +
208 return exists; 209 return exists;
209 } 210 }
210 211
  212 +- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
  213 + dispatch_async(_ioQueue, ^{
  214 + BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
  215 + if (completionBlock) {
  216 + dispatch_async(dispatch_get_main_queue(), ^{
  217 + completionBlock(exists);
  218 + });
  219 + }
  220 + });
  221 +}
  222 +
211 - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key { 223 - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
212 return [self.memCache objectForKey:key]; 224 return [self.memCache objectForKey:key];
213 } 225 }
@@ -214,12 +214,47 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; @@ -214,12 +214,47 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager];
214 - (BOOL)isRunning; 214 - (BOOL)isRunning;
215 215
216 /** 216 /**
217 - * Check if image has already been cached 217 + * Check if image has already been cached
  218 + *
  219 + * @param url image url
  220 + *
  221 + * @return if the image was already cached
218 */ 222 */
219 - (BOOL)cachedImageExistsForURL:(NSURL *)url; 223 - (BOOL)cachedImageExistsForURL:(NSURL *)url;
  224 +
  225 +/**
  226 + * Check if image has already been cached on disk only
  227 + *
  228 + * @param url image url
  229 + *
  230 + * @return if the image was already cached (disk only)
  231 + */
220 - (BOOL)diskImageExistsForURL:(NSURL *)url; 232 - (BOOL)diskImageExistsForURL:(NSURL *)url;
221 233
222 /** 234 /**
  235 + * Async check if image has already been cached
  236 + *
  237 + * @param url image url
  238 + * @param completionBlock the block to be executed when the check is finished
  239 + *
  240 + * @note the completion block is always executed on the main queue
  241 + */
  242 +- (void)cachedImageExistsForURL:(NSURL *)url
  243 + completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
  244 +
  245 +/**
  246 + * Async check if image has already been cached on disk only
  247 + *
  248 + * @param url image url
  249 + * @param completionBlock the block to be executed when the check is finished
  250 + *
  251 + * @note the completion block is always executed on the main queue
  252 + */
  253 +- (void)diskImageExistsForURL:(NSURL *)url
  254 + completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
  255 +
  256 +
  257 +/**
223 *Return the cache key for a given URL 258 *Return the cache key for a given URL
224 */ 259 */
225 - (NSString *)cacheKeyForURL:(NSURL *)url; 260 - (NSString *)cacheKeyForURL:(NSURL *)url;
@@ -71,6 +71,42 @@ @@ -71,6 +71,42 @@
71 return [self.imageCache diskImageExistsWithKey:key]; 71 return [self.imageCache diskImageExistsWithKey:key];
72 } 72 }
73 73
  74 +- (void)cachedImageExistsForURL:(NSURL *)url
  75 + completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
  76 + NSString *key = [self cacheKeyForURL:url];
  77 +
  78 + BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
  79 +
  80 + if (isInMemoryCache) {
  81 + // making sure we call the completion block on the main queue
  82 + dispatch_async(dispatch_get_main_queue(), ^{
  83 + if (completionBlock) {
  84 + completionBlock(YES);
  85 + }
  86 + });
  87 + return;
  88 + }
  89 +
  90 + [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
  91 + // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
  92 + if (completionBlock) {
  93 + completionBlock(isInDiskCache);
  94 + }
  95 + }];
  96 +}
  97 +
  98 +- (void)diskImageExistsForURL:(NSURL *)url
  99 + completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
  100 + NSString *key = [self cacheKeyForURL:url];
  101 +
  102 + [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
  103 + // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
  104 + if (completionBlock) {
  105 + completionBlock(isInDiskCache);
  106 + }
  107 + }];
  108 +}
  109 +
74 - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url 110 - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
75 options:(SDWebImageOptions)options 111 options:(SDWebImageOptions)options
76 progress:(SDWebImageDownloaderProgressBlock)progressBlock 112 progress:(SDWebImageDownloaderProgressBlock)progressBlock