俗套的SDWebImage源码分析

f自己认为SDWebImage主要分为用户使用的接口模块、全局管理类、缓存模块、下载模块、图片解压缩模块。如下图(P.S.图画的不好,大家担待啊)


SDWebImage架构图.png
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,
SDWebImageManager的类图

这个类由于也是用户可以使用的,所以SDImageCache、SDWebImageDownloader两个类中暴露出来了下载图片,缓存存取、查询等方法,而避免直接跨模块调用,平时我们去写代码要注意

  • loadImageWithURL: options: progress: completed: 方法是这个类最重要的方法
    逻辑流程图如下

loadImage流程图

  • 在判断url是否为failURL的逻辑中的failURL是用NSSet作为集合类
    这有两个好处:
  1. 能够确保URL的唯一性
  2. 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.下载模块

待续

你可能感兴趣的:(俗套的SDWebImage源码分析)