SDWebImage源码分析(1)——查找缓存和网络加载
2020-12-30 本文已影响0人
无悔zero
SDWebImage是很常用的一个加载图片的框架,特别是在列表中,非常需要它。我们一起来看看它的源码流程,看看下面例子:
[self.image sd_setImageWithURL:nil];
- 直接从入口找到这:
@implementation UIImageView (WebCache)
...
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
context:context
setImageBlock:nil
progress:progressBlock
completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType, imageURL);
}
}];
}
@implementation UIView (WebCache)
...
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
...
[self sd_cancelImageLoadOperationWithKey:validOperationKey];//先取消旧的任务
self.sd_imageURL = url;
//如果不是延迟设置,就直接在主线程设置占位图
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];//设置占位图
});
}
if (url) {
...
#if SD_UIKIT || SD_MAC
[self sd_startImageIndicator];//开启指示器
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
SDWebImageManager *manager = context[SDWebImageContextCustomManager];//管理者
...
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
...
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];//更新显示进度
});
}
...
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
if (!self) { return; }
// if the progress not been updated, mark it to complete state
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
if (finished) {
[self sd_stopImageIndicator];//停止指示器
}
#endif
...
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];//保存operation
} else { ... }
}
然后可以看到,先进行取消任务,处理细节UI
,然后使用SDWebImageManager
来处理图片,并返回SDWebImageOperation
。
-
SDWebImageOperation
最后会保存到SDOperationsDictionary
中:
@implementation UIView (WebCacheOperation)
...
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];//添加
}
}
}
}
- 接着来看
SDWebImageOperation
是怎么创建和启动的:
@implementation SDWebImageManager
...
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
...
//耦合缓存查找任务和网络加载任务
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
...
//查找缓存和网络加载
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
return operation;
}
返回的是SDWebImageCombinedOperation
,其作用其实就是耦合了loaderOperation
网络加载任务和cacheOperation
缓存查找任务:
@interface SDWebImageCombinedOperation ()
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> loaderOperation;
@property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> cacheOperation;
@property (weak, nonatomic, nullable) SDWebImageManager *manager;
@end
- 如果需要查找缓存,
SDWebImageManager
会先查找缓存,再网络加载图片;否则,直接网络加载图片:
@implementation SDWebImageManager
...
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
...
if (shouldQueryCache) {
NSString *key = [self cacheKeyForURL:url context:context];//获取图片对应的key
//先查找缓存
@weakify(operation);
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
...
// 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];
}
}
- 先来看看查找任务:
@implementation SDWebImageManager
...
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
...
return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}
@implementation SDImageCache
...
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
...
if (queryCacheType != SDImageCacheTypeDisk) {
image = [self imageFromMemoryCacheForKey:key];//首先检查内存缓存
}
...
void(^queryDiskBlock)(void) = ^{
...
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];//然后检查磁盘缓存
...
}
};
// Query in ioQueue to keep IO-safe
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
5-1. 首先会查找内存缓存:
@implementation SDImageCache
...
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memoryCache objectForKey:key];
}
@implementation SDMemoryCache
...
- (id)objectForKey:(id)key {
...
if (key && !obj) {
// Check weak cache
SD_LOCK(_weakCacheLock);
obj = [self.weakCache objectForKey:key];//从内存缓存获取
SD_UNLOCK(_weakCacheLock);
...
}
}
return obj;
}
5-2. 再去查找磁盘缓存:
@implementation SDImageCache
...
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
...
NSData *data = [self.diskCache dataForKey:key];
...
return data;
}
@implementation SDDiskCache
...
- (NSData *)dataForKey:(NSString *)key {
NSParameterAssert(key);
NSString *filePath = [self cachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];//从沙盒读取
...
return nil;
}
- 回到第4步,如果没有找到缓存,自然就去网络加载图片:
@implementation SDWebImageManager
...
- (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 {
...
if (shouldDownload) {
...
//创建loaderOperation
operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { ... }
} else if (cachedImage) {
...
} else { ... }
@implementation SDWebImageDownloader {
...
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
...
return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}
6-1. 到这一步,创建了网络加载任务的operation
,然后添加到self.downloadQueue
中,添加后内部会自动的调用start
方法执行任务:
@implementation SDWebImageDownloader {
...
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
...
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
...
[self.downloadQueue addOperation:operation];//添加后内部会自动的调用start方法执行任务
} else { ... }
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];//包装operation
...
return token;
}
- 返回时,用
SDWebImageDownloadToken
包装了网络加载任务的operation
。我们可以发现,SDWebImageCombinedOperation
的loaderOperation
和cacheOperation
都不是真正意义上的网络加载任务和缓存查找任务,创建时cacheOperation
就已经进行缓存查找,loaderOperation
则是包装了网络加载任务的SDWebImageDownloadToken
。
6-2. 在创建SDWebImageDownloaderOperation
(网络加载任务的operation
)时,默认情况下网络缓存策略会设置为NSURLRequestReloadIgnoringLocalCacheData
,防止重复缓存:
@implementation SDWebImageDownloader {
...
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context {
...
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;//默认不设置,防止重复缓存,所以默认忽略本地缓存
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
...
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
...
return operation;
}
6-3. 回到第6-1步,创建完SDWebImageDownloaderOperation
添加到self.downloadQueue
后,自动执行SDWebImageDownloaderOperation
的start
方法,开始网络加载图片:
@implementation SDWebImageDownloaderOperation
...
- (void)start {
@synchronized (self) {
...
NSURLSession *session = self.unownedSession;
if (!session) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];//创建session
self.ownedSession = session;
}
...
self.dataTask = [session dataTaskWithRequest:self.request];//创建task
self.executing = YES;
}
if (self.dataTask) {
...
[self.dataTask resume];//启动请求
...
} else ... }
简单来说,SDWebImage处理图片时,会先取消旧的任务,处理指示器和进度,接着优先查找缓存显示图片,再通过网络加载显示图片。
- 补充
网络请求时,在下面回调中把NSCachedURLResponse
等于nil
可以避免产生本地缓存:
·