f自己认为SDWebImage主要分为用户使用的接口模块、全局管理类、缓存模块、下载模块、图片解压缩模块。如下图(P.S.图画的不好,大家担待啊)
1. 关于用户的接口(或者说是提供给库用户的api)模块:
这个模块主要给用户提供了对UIImageView、UIButton等UIView子类设置各种图片的便捷方法,其中UIView+WebCache里面的已下方法被UIImageView、UIButton等调用
/*这个方法在UIImageView、UIButton的category中调用,
把已下代码写在UIView WebCache中是为了重用*/
- (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的话就会使用自己的类型作为key
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
/*
在UIView+WebCacheOperation中,作者通过runtime为UIView添加了operationDictionary属性,具体看源码
为什么是字典,应该是为了UIButton服务的,毕竟UIButton里可以对于不同ControlState设置图片的(总之UIButton是对应多个图片的)
没什么好说的,先取消掉之前的Operation,这里的operation不是
NSOperation,是SDWebImageCombinedOperation,具体的后面说*/
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/*
runtime关联了self和url,这个Category中大量的使用了对象关联
如activityIndicator如是
*/
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
//定义的一个宏 异步回到主线程
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
...
//避免循环引用
__weak __typeof(self)wself = self;
id 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;
...
});
}];
//把operation添加到字典里
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
...
}
}
- 这里要说的是- (void)sd_setImage:
imageData: basedOnClassOrViaCustomSetImageBlock方法
//这个方法设置了UIImageView、UIButton的image,同时setImageBlock这个block为上层提供了灵活设置图片入口
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
if (setImageBlock) {
setImageBlock(image, imageData);
return;
}
#if SD_UIKIT || SD_MAC
if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
imageView.image = image;
}
#endif
#if SD_UIKIT
if ([self isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)self;
[button setImage:image forState:UIControlStateNormal];
}
#endif
}
- UIView+WebCacheOperation这个category为UIView及其子类提供了直接取消下载操作的方法
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id operation in operations) {
if (operation) {
//这里的operation是SDWebImageCombinedOperation
//其内部调用了SDWebImageDownloaderOperation的cancel方法
[operation cancel];
}
}
}
...
}
}
2.全局管理类即SDWebImageManager类
这个类的头文件里定义了SDWebImageOptions,option的所有含义可以在这个博客看(这里就不在赘述了)http://blog.csdn.net/iosworker/article/details/51942463
- 今天在看代码时发现对SDWebImageRetryFailed(禁用黑名单),理解错了,一直以为是下载失败后会自动重新下载,结果发现不是,这是作者的注释
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
默认当一个URL下载失败,这个URL会加入黑名单,所以本库不会继续下载
* This flag disable this blacklisting.
这个是禁用URL黑名单
*/
SDWebImageRetryFailed = 1 << 0,
这个类由于也是用户可以使用的,所以SDImageCache、SDWebImageDownloader两个类中暴露出来了下载图片,缓存存取、查询等方法,而避免直接跨模块调用,平时我们去写代码要注意
- loadImageWithURL: options: progress: completed: 方法是这个类最重要的方法
逻辑流程图如下
- 在判断url是否为failURL的逻辑中的failURL是用NSSet作为集合类
这有两个好处:
- 能够确保URL的唯一性
- NSSet内部是由Hash Map实现的,查找速度快
@property (strong, nonatomic, nonnull) NSMutableSet *failedURLs;
- SDWebImageCombinedOperation类
@interface SDWebImageCombinedOperation : NSObject
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
//这个取消block是用于下载的操作的取消
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
//这个Operation是用于取消从硬盘获取缓存
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end
这个类封装了取消下载、取消从硬盘获取缓存的代码,实现了SDWebImageOperation的cancel协议方法,避免了上层的UIView+WebCacheOperation直接调用底层的代码,如下
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
/*
这是取消从硬盘获取,具体在SDImageCache
- (NSOperation *)queryCacheOperationForKey: done: #387 可见
*/
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
//这个是取消下载的block
self.cancelBlock();
_cancelBlock = nil;
}
}
- - (void)callCompletionBlockForOperation: completion: image: data: error: cacheType:finished: url:
//这个方法是执行completionBlock,可以认为是设置图片的
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
image:(nullable UIImage *)image
data:(nullable NSData *)data
error:(nullable NSError *)error
cacheType:(SDImageCacheType)cacheType
finished:(BOOL)finished
url:(nullable NSURL *)url {
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completionBlock) {
completionBlock(image, data, error, cacheType, finished, url);
}
});
}
- loadImageWithURL:options:progress:completed:是SDWebImageManager的关键方法,以上流程图的逻辑都在此方法内实现
关于合理的制造bug(NSAssert)
// SDWebImageManager
// loadImageWithURL:options:progress:completed: #110
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
看了yishuiliunian(不太清楚这位大神叫什么)的《如何合理的制造BUG》一文http://dzpqzb.com/2015/11/11/make-bugs-correct.html
再结合自己项目终遇到的bug,使用NSAssert来制造bug,对于框架来说这样很严谨,对于自己来说能崩溃的bug不是更容易找到吗?
- 当operation被cancel时,是不调用callCompletionBlockForOperation: completion: image: data: error: cacheType:finished: url: 去执行cancelBlock的 ,仅仅只是把这个Operation从数组中移出掉
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
...略...
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
中间有段是将SDWebImageOptions转换成SDWebImageDownloaderOptions的代码,其中包含多种位运算,位运算请看https://zh.wikipedia.org/wiki/%E4%BD%8D%E6%93%8D%E4%BD%9C
这个是取消下载的block,请与SDWebImageCombinedOperation 对照看
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
3.下载模块
待续