SDWebImage简单分析

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

SDWebImage代码结构:


SDWebImage简单分析_第1张图片
SDWebImage结构.png

SDWebImage工作流程图:


SDWebImage简单分析_第2张图片
流程图.jpg

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

一、manager类

@interface SDWebImageManager ()

@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet *failedURLs;
@property (strong, nonatomic, nonnull) NSMutableArray *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 *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  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

你可能感兴趣的:(SDWebImage简单分析)