很大众的一个第三方,确实很方便。闲着没事看看源码,简单分析加载图片的过程。
PS:别处盗的图,好多关于SDWebimage的文字都会引用这个图,确实总结的很好,一看流程图就明白
SDWebImage代码结构:
SDWebImage工作流程图:
看了上面图片,感觉代码设计的结构很合理,流程也很合理,考虑了各种情况,容错性很好。别人大牛写的代码就是厉害。我只是用来膜一下。
一、manager类
@interface SDWebImageManager ()
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;
@end
通过代码声明的属性,很容易发现,manager类其实就4个属性,一个缓存工具,一个下载工具,一个错误url数组,一个任务数组。各自的作用一看就明白了。
二、缓存工具SDImageCache
查看属性就知道它的结构,主要的是缓存配置信息,比如设置的缓存大小,缓存多少时间,路径等。主要还有几个方法,一个是添加缓存到内存和硬盘,从内存和硬盘取出缓存。
比如,这个从硬盘取出图片
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
// 根据url拼接路径
NSString *defaultPath = [self defaultCachePathForKey:key];
// 很顺利,直接从硬盘中获得data数据
NSData *data = [NSData dataWithContentsOfFile:defaultPath];
if (data) {
return data;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
// 如果硬盘中还没找到,就追加文件扩展名接续找
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension];
if (data) {
return data;
}
// 如果硬盘中还没找到,就根据自定义路径继续查找
NSArray<NSString *> *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
if (imageData) {
return imageData;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension];
if (imageData) {
return imageData;
}
}
return nil;
}
可以发现,写的代码考虑了各种情况,并做了应对措施。很全面。
三、下载器SDWebImageDownloader
主要通过downloadImageWithURL这个方法,新建NSMutableURLRequest请求进行下载。
其中它有个SDWebImageDownloaderOperation字典属性,根据URL用来存放下载任务。每个URL都有对应的下载任务。
通过在SDWebImageDownloader建立request请求,最后SDWebImageDownloaderOperation继承自NSOperation下载图片任务。SDWebimage中用来网络请求,下载图片的任务。
其中start方法启动下载任务。
[self.dataTask resume];
1、代码中用来加载图片
NSString *url = @"http://pic2.ooopic.com/12/22/94/37bOOOPICc7_1024.jpg";
self.imgv = [[UIImageView alloc] initWithFrame:CGRectMake(30, 100, 150, 150)];
[self.imgv sd_setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage imageNamed:@"111"]];
[self.view addSubview:self.imgv];
很简单,图片就显示出来了。
2、进入这个方法,查看源码,发现这段代码
根据自己的理解,加了注释,看着方便些,都是简单易懂的东西。
- (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 {
// 根据任务key值,先去结束这个imageview绑定的上一个任务
// 如果不存在key字符串,就用当前类名新建
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 通过关联对象方法,给当前imageView绑定一个url属性
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 第一步: 先用palceholder图片显示图片
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
// 第二步:通过url字符串,去查找缓存或者从新下载图片,得到图片后再显示图片
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
// 通过调用manager单例,获得图片
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
这个方法在UIView+WebCache中,在UIView的分类category中。
代码中,通过runtime关联对象方法,给当前这个对象动态添加了个url字符串属性。所以每个imageview上都有个url属性。
在添加url之前,先通过绑定的一个字典,取消了其他operation任务,比如在对通一个imageview反复加载图片,就得取消前面的操作,只让最后一个操作生效,所以得先取消imageview的其他的任务。通过代码可以看到这个operationDictionary字典也是通过关联对象方法动态添加的属性。这个字典用来存储imageview的任务。
3、这个方法主要是通过manager单例的loadImageWithURL方法根据url获得图片
// 通过缓存类,根据key值 即url查找图片。
// 得到缓存操作对应的operation任务,赋值给当前imageview的类扩展
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//如果操作被取消,从数组从移除当前operation任务对象,直接返回,结束操作
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
//缓存中图片不存在或者需要刷新图片 并且代理对象响应了需要下载图片的操作,就去重新下载图片
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
// 如果是被要求刷新缓存,先把缓存图片回调出去,imageview暂时显示缓存图片
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:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// 重新下载download
。。。
过程和图上一样,先查缓存,没有缓存就启动下载器进行下载
4、下载图片
self.imageDownloader downloadImageWithURL:url。通过url进行网络下载图片
在这部分代码中,会新建SDWebImageDownloaderOperation任务
5、代码太多,各种类,说着费劲,直接看源码看着还方便些。有空接着写。
UIImageVIew通过分类获得额外2个属性,一个url,一个dictionary<url:operation>