SDWebImage

SDImageCacheHow do memory lookups and disk lookups work

Memory to find

  1. SDImageCachethroughmemoryCacheFind, because inherit fromNSCache, directly called[super objectForKey:key]To find it, and whether or not you find it, you’re going to go part two.
  2. shouldUseWeakMemoryCacheIf set toYES, it will goweakCacheTo find the
SD_LOCK(_weakCacheLock);
obj = [self.weakCache objectForKey:key];
SD_UNLOCK(_weakCacheLock);
Copy the code

SDMemoryCacheInternal lookup methods (skipped without reading)

- (id)objectForKey:(id)key { id obj = [super objectForKey:key]; if (! self.config.shouldUseWeakMemoryCache) { return obj; } if (key && ! obj) { // Check weak cache SD_LOCK(_weakCacheLock); obj = [self.weakCache objectForKey:key]; SD_UNLOCK(_weakCacheLock); if (obj) { // Sync cache NSUInteger cost = 0; if ([obj isKindOfClass:[UIImage class]]) { cost = [(UIImage *)obj sd_memoryCost]; } [super setObject:obj forKey:key cost:cost]; } } return obj; }Copy the code

Disk to find

  1. So let’s look at data. throughdiskImageDataForKey:In the methodioQueueQueue to find, find is bydispatch_syncSynchronous search
  2. Generate image from data, this step is mainly to doDecodeRelated operations
  3. Whether to cache in memory
  4. Return to picture
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
    NSData *data = [self diskImageDataForKey:key];
    UIImage *diskImage = [self diskImageForKey:key data:data options:options context:context];
    
    BOOL shouldCacheToMomery = YES;
    if (context[SDWebImageContextStoreCacheType]) {
        SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
        shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
    }
    if (diskImage && self.config.shouldCacheImagesInMemory && shouldCacheToMomery) {
        NSUInteger cost = diskImage.sd_memoryCost;
        [self.memoryCache setObject:diskImage forKey:key cost:cost];
    }

    return diskImage;
}
Copy the code

SDImageCacheHow does caching images to memory and disk work

The following code can be read without looking at the document to understand the general process

  1. Check whether the picture and key exist first. Key is generally the address of the picture
  2. Whether to cache in memory this time, and whether the overall configuration can cache in memory (usually memory caching is enabled)
  3. If the image is cached to disk, it will not be cached to disk again

[self.memoryCache setObject:image forKey:key cost:cost]; Async: dispatch_async: self.ioQueue: async: self.ioQueue: async: self.ioQueue _ioQueue = dispatch_queue_create(“com.hackemist.SDImageCache”, DISPATCH_QUEUE_SERIAL);

The overall process is as follows

- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toMemory:(BOOL)toMemory toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { if (! image || ! key) { if (completionBlock) { completionBlock(); } return; } // if memory cache is enabled if (toMemory && self.config.shouldCacheImagesInMemory) { NSUInteger cost = image.sd_memoryCost; [self.memoryCache setObject:image forKey:key cost:cost]; } if (! toDisk) { if (completionBlock) { completionBlock(); } return; } dispatch_async(self.ioQueue, ^{ @autoreleasepool { NSData *data = imageData; if (! data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) { // If image is custom animated image class, prefer its original animated data data = [((id<SDAnimatedImage>)image) animatedImageData]; } if (! data && image) { // Check image's associated image format, may return .undefined SDImageFormat format = image.sd_imageFormat; if (format == SDImageFormatUndefined) { // If image is animated, use GIF (APNG may be better, But has bugs before macOS 10.14) if (image.sd_isanimated) {format = SDImageFormatGIF; } else { // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG; } } data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil]; } [self _storeImageDataToDisk:data forKey:key]; [self _archivedDataWithImage:image forKey:key]; } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); }}); }Copy the code

SDWebImageManagerHow is the query cache, download string (a bit of code, can not see)

Cache and download concatenation

  1. Query normal cache process
  2. Continue download process

Regardless of whether the cache, will walk the callDownloadProcessForOperation to determine whether to download below

callDownloadProcessForOperation: url: options: context: cachedImage: cachedData: cacheType: progress: completed:
Copy the code

ifcachedImageThere is,shouldDownloadWill be set tofalse

CacheImage exists and will not actually be downloaded

// Query normal cache process - (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock  { // Grab the image cache to use id<SDImageCache> imageCache; if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache]; } else { imageCache = self.imageCache; } // Get the query cache type SDImageCacheType queryCacheType = SDImageCacheTypeAll; if (context[SDWebImageContextQueryCacheType]) { queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue]; } // Check whether we should query cache BOOL shouldQueryCache = ! SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); if (shouldQueryCache) { NSString *key = [self cacheKeyForURL:url context:context]; @weakify(operation); operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) { @strongify(operation); if (! operation || operation.isCancelled) { // Image combined operation cancelled by user [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain  code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url]; [self safelyRemoveOperationFromRunning:operation];  return; } else if (context[SDWebImageContextImageTransformer] && ! cachedImage) { // Have a chance to query original cache instead of downloading [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock]; return;  } // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock]; }]; } else { // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock]; }}Copy the code

CallDownloadProcessForOperation methods related code, in view of the tandem process at the core place in shouldDownload judgment

// Download process - (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context cachedImage:(nullable UIImage *)cachedImage cachedData:(nullable NSData *)cachedData cacheType:(SDImageCacheType)cacheType progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image loader to use id<SDImageLoader> imageLoader; if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { imageLoader = context[SDWebImageContextImageLoader]; } else { imageLoader = self.imageLoader; } // Check whether we should download image from network BOOL shouldDownload = ! SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly); shouldDownload &= (! cachedImage || options & SDWebImageRefreshCached); shouldDownload &= (! [self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) { shouldDownload &= [imageLoader  canRequestImageForURL:url options:options context:context]; } else { shouldDownload &= [imageLoader canRequestImageForURL:url]; } if (shouldDownload) { if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image. SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage; context = [mutableContext copy]; } @weakify(operation); operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { @strongify(operation);  if (! operation || operation.isCancelled) { // Image combined operation cancelled by user [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain  code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];  } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) { // Download operation cancelled by user before sending the request, don't block failed URL [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];  } else if (error) { [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];  BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];  if (shouldBlockFailedURL) { SD_LOCK(self->_failedURLsLock); [self.failedURLs addObject:url];  SD_UNLOCK(self->_failedURLsLock); } } else { if ((options & SDWebImageRetryFailed)) { SD_LOCK(self->_failedURLsLock);  [self.failedURLs removeObject:url]; SD_UNLOCK(self->_failedURLsLock);  } // Continue store cache process [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock]; } if (finished) { [self safelyRemoveOperationFromRunning:operation]; } }]; } else if (cachedImage) { [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; } else { // Image not in cache and download disallowed by delegate [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; }}Copy the code

There are two steps to cache lookup:

  1. First check the in-memory cache…
  2. Second check the disk cache…
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock { if (! key) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return nil; } // Invalid cache type if (queryCacheType == SDImageCacheTypeNone) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return nil; } // First check the in-memory cache... UIImage *image; if (queryCacheType ! = SDImageCacheTypeDisk) { image = [self imageFromMemoryCacheForKey:key]; } if (image) { if (options & SDImageCacheDecodeFirstFrameOnly) { // Ensure static image Class animatedImageClass = image.class; if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) { #if SD_MAC image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation]; #endif } } else if (options & SDImageCacheMatchAnimatedImageClass) { // Check image class matching Class animatedImageClass = image.class; Class desiredImageClass = context[SDWebImageContextAnimatedImageClass]; if (desiredImageClass && ! [animatedImageClass isSubclassOfClass:desiredImageClass]) { image = nil; } } } BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && ! (options & SDImageCacheQueryMemoryData)); if (shouldQueryMemoryOnly) { if (doneBlock) { doneBlock(image, nil, SDImageCacheTypeMemory); } return nil; } // Second check the disk cache... NSOperation *operation = [NSOperation new]; // Check whether we need to synchronously query disk // 1. in-memory cache hit & memoryDataSync // 2. in-memory cache miss & diskDataSync BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) || (! image && options & SDImageCacheQueryDiskDataSync)); void(^queryDiskBlock)(void) = ^{ if (operation.isCancelled) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return; } @autoreleasepool { NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key]; UIImage *diskImage; if (image) { // the image is from in-memory cache, but need image data diskImage = image; } else if (diskData) { BOOL shouldCacheToMomery = YES; if (context[SDWebImageContextStoreCacheType]) { SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue]; shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory); } // decode image data only if in-memory cache missed diskImage = [self diskImageForKey:key data:diskData options:options context:context]; if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) { NSUInteger cost = diskImage.sd_memoryCost; [self.memoryCache setObject:diskImage forKey:key cost:cost]; } } if (doneBlock) { if (shouldQueryDiskSync) { doneBlock(diskImage, diskData, SDImageCacheTypeDisk); } else { dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, diskData, SDImageCacheTypeDisk); }); }}}}; // Query in ioQueue to keep IO-safe if (shouldQueryDiskSync) { dispatch_sync(self.ioQueue, queryDiskBlock); } else { dispatch_async(self.ioQueue, queryDiskBlock); } return operation; }Copy the code