SDWebImage简单分析

很大众的一个第三方,确实很方便。闲着没事看看源码,简单分析加载图片的过程。
PS:别处盗的图,好多关于SDWebimage的文字都会引用这个图,确实总结的很好,一看流程图就明白

SDWebImage代码结构:


SDWebImage结构.png

SDWebImage工作流程图:


流程图.jpg

看了上面图片,感觉代码设计的结构很合理,流程也很合理,考虑了各种情况,容错性很好。别人大牛写的代码就是厉害。我只是用来膜一下。

一、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、代码太多,各种类,说着费劲,直接看源码看着还方便些。有空接着写。


10D1E51742B8F67DB41F7928B98392AD.gif

UIImageVIew通过分类获得额外2个属性,一个url,一个dictionary<url:operation>

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

推荐阅读更多精彩内容