SDWebImage
SDImageCache
How do memory lookups and disk lookups work
Memory to find
SDImageCache
throughmemoryCache
Find, 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.shouldUseWeakMemoryCache
If set toYES
, it will goweakCache
To find the
SD_LOCK(_weakCacheLock);
obj = [self.weakCache objectForKey:key];
SD_UNLOCK(_weakCacheLock);
Copy the code
SDMemoryCache
Internal 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
- So let’s look at data. through
diskImageDataForKey:
In the methodioQueue
Queue to find, find is bydispatch_sync
Synchronous search - Generate image from data, this step is mainly to do
Decode
Related operations - Whether to cache in memory
- 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
SDImageCache
How does caching images to memory and disk work
The following code can be read without looking at the document to understand the general process
- Check whether the picture and key exist first. Key is generally the address of the picture
- Whether to cache in memory this time, and whether the overall configuration can cache in memory (usually memory caching is enabled)
- 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
SDWebImageManager
How is the query cache, download string (a bit of code, can not see)
Cache and download concatenation
- Query normal cache process
- 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
ifcachedImage
There is,shouldDownload
Will 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:
- First check the in-memory cache…
- 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