简介
SDWebImage 是一个非常强大的图片加载框架,从一开始工作都在用它,都知道他是先内存检查 再磁盘检查 都没有最后才走网络下载,那么他实现的代码究竟是什么逻辑呢,今天我们就来分析他的核心代码。
主讲5个类
- UIImageView+WebCache.h
- UIView+WebCache.h
- UIView+WebCacheOperation.h
- SDWebImageManager.h
- SDWebImageDownloader.h
- SDWebImageDownloaderOperation.m
UIImageView+WebCache.h
这里不管调用任何方法 最终都会到下面这个方法中
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock{}
通过这个类的查看发现 SD的代码复用很高 也发现了人家的设计技巧 从最原始的没有placeholderImage 到 没有SDWebImageOptions 一层一层调用 抛出来的API非常易用。
*** UIView+WebCache.h ***
调用 sd_setImageWithURL 最终会到 下面这个方法。接下来我们分析这个方法里的代码 我把一些判断信息 和进度 信息去掉
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context {
}
== 代码分析 ==
这一行是取消当前view上的下载操作 如果之前已经有下载任务 现在再来一个下载任务 之前的下载完全没有必要
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
用于管理图片下载 上层调用API
manager = [SDWebImageManager sharedManager];
接下来执行下边的方法
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {}
来到SDWebImageManager.h 代码分析
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
BOOL isFailedUrl = NO; 判断是否走失败逻辑
SD_LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation]; 将正在进行的操作加入到队列中
SD_UNLOCK(self.runningOperationsLock);
下面分析这个方法 这里是决定我们从缓存 磁盘 还是网络拿图片的入口
[self callCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
callCacheProcessForOperation
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0; 判断是否可以拿缓存 我们这里只看可以拿缓存的情况
if (shouldQueryCache) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter]; // 拿到缓存图片key
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
}];
}
来到SDImageCache 的queryCacheOperationForKey方法
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock{
从内存缓存中拿
UIImage *image = [self imageFromMemoryCacheForKey:key];
检查是否允许磁盘查找
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
从磁盘查找
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
如果内存中没有就把磁盘中的读出 重新放入内存缓存中
if (image) {
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
}
执行回调
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
来到SDWebImageManager.h callDownloadProcessForOperation
这一大段都是用来判断有没有必要从网络读取图片的操作
BOOL shouldDownload = (options & SDWebImageFromCacheOnly) == 0;
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [self.imageLoader canRequestImageForURL:url];
接下来分析下面的方法
operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage
来到SDWebImageDownloader requestImageWithURL
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
}
self.URLOperations[url] = operation; 设置键值映射 以url为键。操作为值 用于判断当同一个url的操作已经有了就不用再执行了
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
这一行代码很重要 将所有的回调方法进行保存
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
执行了下边这行代码 此时 下载任务开始。
[self.downloadQueue addOperation:operation];
来到SDWebImageDownloaderOperation.m
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
SD_LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks]; 数组中包含了以 completed 为键 以回调函数为值的字典 一会会用到
SD_UNLOCK(self.callbacksLock);
return callbacks;
}
基于 NSURLSession 对图片进行下载
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
self.dataTask = [session dataTaskWithRequest:self.request];
[self.dataTask resume];
基于系统的 session 执行下载任务
下载成功回调
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSData *imageData = [self.imageData copy]; // 在另一个回调方法中 已经给 self.imageData赋值了
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
// 下边这个方法就是将addHandlersForProgress所有加入的回调 全部执行一遍
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
补充一个方法
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
#if SD_UIKIT || SD_MAC
[self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:cacheType imageURL:imageURL];
#else
// watchOS does not support view transition. Simplify the logic
if (setImageBlock) {
setImageBlock(image, imageData, cacheType, imageURL);
} else if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
[imageView setImage:image];
}
#endif
}
当所有的下载都完成了以后会调用上边的方法 将下载好的图片进行赋值