SDWebImage 源码阅读(一)

1. 前言

一直没有系统的阅读过整套源码,仅仅是看过几篇总结的文章,了解了设计思路,年前项目刚结束,终于有大块时间仔细阅读一下 SDWebImage 框架了。开始阅读的时候感觉很简单,也没有添加自己的注释,最后越看越乱,发现很多细节处理和逻辑完整性和我之前自己思考的不太一样,就这样乱糟糟的看了一下午,感觉这样通读的效果不是很好。今天换了一种思维来理解,看别人的源码和做英语阅读一样,先笼统的浏览一遍,然后带着问题阅读,效果特别好。简单看了一遍,记录一下学习笔记。

2. 概况

首先看 UIImageView+WebCache 这个扩展类,给 imageView 添加图片的方法一共有以下几种:

- (void)sd_setImageWithURL:(NSURL *)url;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;

- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

以上方法最后都会调用下面的方法:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

也就可以确定,这个方法就是我们阅读源码的入口了。大概知道这些参数都是什么意思:

  • url 和 placeholder 不用再介绍了

  • SDWebImageOptions 应该是枚举,进入所在类查看,是位掩码,每个字段含义不明确,后面再分析

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    ...
};
  • SDWebImageDownloaderProgressBlock 表示正在下载的时候所要处理的事情

  • SDWebImageCompletionBlock 表示下载完成后所要做的事

3. 详解

接下来看看方法里面的具体实现:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {

    // 先将原来的 operation 停止(详情见 3.1)
    [self sd_cancelCurrentImageLoad];

    // 给 imageView 关联要下载的图片的url
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    // SDWebImageDelayPlaceholder : 占位图延迟加载标识
    // 默认情况下,占位图先赋值给imageView,然后加载图像。这个标志是图像加载完毕之前占位图不先赋值给imageVIew。

    // 在没有发送请求获取网络端图片之前、如果 options 不等于 SDWebImageDelayPlaceholder
    if (!(options & SDWebImageDelayPlaceholder)) {

        // 刚看到这里懵逼了一下,印象中没有 dispatch_main_async_safe 这个方法啊,进去发现是一个宏定义,目的是UI相关操作要在主线程,没啥技术点,略过
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }


    // 如果 url 存在
    if (url) {

        // setShowActivityIndicatorView: 暴露在头文件中,设置加载图片的时候是否在 imageView 中央添加菊花
        // showActivityIndicatorView : 是 setShowActivityIndicatorView 的 get 方法、返回 BOOL
        // 此状态是用 runtime 关联到 imageView 上的

        // check if activityView is enabled or not
        // 是否添加菊花
        if ([self showActivityIndicatorView]) {

            // 添加菊花
            [self addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        // 下载图片(详情见 3.2)
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

            // 图片加载完成、移除菊花
            [wself removeActivityIndicator];

            if (!wself) return;

            dispatch_main_sync_safe(^{
                if (!wself) return;

                // SDWebImageAvoidAutoSetImage : 不允许自动将 image 添加到 imageView
                // 默认情况下,图像下载后直接添加到 imageView,但是在某些情况下,我们希望在设置图像之前先有交互(例如应用滤镜或添加交叉淡入淡出动画),如果要在成功完成时手动设置图像,请使用此标志.

                // 如果图片下载成功 并且 (options = SDWebImageAvoidAutoSetImage)
                // 这个就是我之前常用的方法,获得 image 再赋予 button,现在才发现有 UIButton+WebCache 扩展类
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    // 直接返回 image,不添加到 imagView
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {

                    // 如果image获取成功,直接添加 image 到 imageView
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {

                    // 如果 image获取失败,并且 (options = SDWebImageDelayPlaceholder),才将 placeholder 添加到 imageView,赤裸裸的备胎
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];

        // 将正在下载的 operation 添加到 operation 字典中(详情见 3.3)
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

    } else {  // 如果 url 为空,则抛出错误
        dispatch_main_async_safe(^{

            // 移除菊花
            [self removeActivityIndicator];

            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}
3.1. 将原来的 operation 停止

如果刚刚已经请求了图片,当 imageView 再次加载图片之前,需要取消之前的请求。

进入 sd_cancelCurrentImageLoad 方法

- (void)sd_cancelCurrentImageLoad {
    [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}

再进入 sd_cancelImageLoadOperationWithKey: 方法

- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // Cancel in progress downloader from queue
    // 取消正在进行的下载队列

    // 获取正在下载的队列
    NSMutableDictionary *operationDictionary = [self operationDictionary];

    // 获取 @"UIImageViewImageLoad" 对应的 value
    id operations = [operationDictionary objectForKey:key];
    if (operations) {
        // 如果 value 是数组类型
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                // 如果任务存在,则取消任务
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){  // 如果符合协议
            [(id<SDWebImageOperation>) operations cancel];
        }

        // 最后移除key对应的value
        [operationDictionary removeObjectForKey:key];
    }
}

代码很容易理解,先获取到 operation 的序列,即 [self operationDictionary]。然后根据 key 索引到对应的 operation,如果 operation 存在,就要取消该 operation。这里需要注意的地方就是,索引到的 operation 其实是一组 operation 集合,那么久需要遍历一个个取消序列中的 operation。最后移除 key 对应的 object。

这里有个疑惑:为啥 operation 都是 id ?而且进入 SDWebImageOperation 后发现只有一个 cancel 方法。为什么这样设计,还有待进一步研究。

3.2. 下载图片
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
    ...            
}];

进入后发现这是 SDWebImageManager 的方法,这就是我们继续探索源码核心的下一个入口,这里不做过多介绍,我们将在下一篇中重点介绍。

3.3. 添加新的operation 到队列

进入 sd_setImageLoadOperation: forKey: 方法

- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {

    // 取消正在进行的下载队列
    [self sd_cancelImageLoadOperationWithKey:key];

    NSMutableDictionary *operationDictionary = [self operationDictionary];
    [operationDictionary setObject:operation forKey:key];
}

看到方法内的实现,就感觉没有什么难度了,sd_cancelImageLoadOperationWithKey: 方法在 3.1 中介绍了,接下来将该 operation 添加到下载队列中。

4. 总结

到此为止,这个类中主要的方法、属性都解析出来了。其他的比如运用 runtime 关联属性等等就不一一介绍了,都比较简单。这个扩展类其实没有太多核心思想,主要就是设计了严密的判断,保证运用该框架的其他开发者能够在各种环境下请求图片。遗留问题就是 SDWebImageManager 下载图片方法内的实现,我们将在下一篇着重介绍。

SDWebImage 源码解析 github 地址:https://github.com/Mayan29/SDWebImageAnalysis

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值