俗套的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 <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;
        ...
        });
    }];
    //把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 <SDWebImageOperation> 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<NSURL *> *failedURLs;

  • SDWebImageCombinedOperation类

@interface SDWebImageCombinedOperation : NSObject        <SDWebImageOperation>

@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;
    }
 operation.cancelBlock = ^{
            [self.imageDownloader cancel:subOperationToken];
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self safelyRemoveOperationFromRunning:strongOperation];
        };
3.下载模块

待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容

  • SDWebImage[https://github.com/rs/SDWebImage] 分析 Version 4...
    wyanassert阅读 1,887评论 0 8
  • 技术无极限,从菜鸟开始,从源码开始。 由于公司目前项目还是用OC写的项目,没有升级swift 所以暂时SDWebI...
    充满活力的早晨阅读 12,611评论 0 2
  • 前不久做了一个生成快照的需求,其中用到 SDWebImage 来下载图片,在使用该框架的过程中也遇到了一些问题,索...
    ShannonChenCHN阅读 14,036评论 12 241
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 时光如水, 旧事随风去, 人生路漫, 眨眼又黄昏, 喜怒哀乐都是歌, 何用愁绪添新烦, 朝阳增辉, 秋枯冬寒总有时...
    喜在心间阅读 99评论 0 0