SDWebImage源码分析

作者: 充满活力的早晨 | 来源:发表于2017-10-26 11:16 被阅读124次

    技术无极限,从菜鸟开始,从源码开始。

    由于公司目前项目还是用OC写的项目,没有升级swift 所以暂时SDWebImage 源码分析还是使用OC 版本是4.1.0 。

    1.目录

    1.UIImageView+WebCache

    2.源码分析

    第一次写博客,不知道如何组织文章。想来想去还是从用法开始步步剖析。

    1.UIImageView+WebCache

    先看下面一段sdweb使用的代码

    UIImageView * imageView = [[UIImageView alloc]initWithFrame:self.view.bounds];

    [self.view addSubview:imageView];

    [imageView sd_setImageWithURL:[NSURL URLWithString:@"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=354029391,3444366700&fm=27&gp=0.jpg"]];

    上面的效果就是下载一张图片。那sdwebImage是如何下载图片的?并且将图片加载imageView 上的呢?

    那我们先看看UIImageView的category(webCache)的方法

    1.- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;

    2.- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT

    3.- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;

    4.- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock;

    5.- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;

    6.- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock;

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

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

    9.- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray*)arrayOfURLs

    10.- (void)sd_cancelCurrentAnimationImagesLoad;

    方法好多,那我们一个一个方法看。

    1.- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;

    - (void)sd_setImageWithURL:(nullable NSURL *)url {

    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];

    }

    这个方法很简单,就是调用 下 该类的- (void)sd_setImageWithURL:(nullable NSURL *)url

    placeholderImage:(nullable UIImage *)placeholder

    options:(SDWebImageOptions)options

    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

    completed:(nullable SDExternalCompletionBlock)completedBlock

    方法,而该放的调用到- (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。

    真正的方法实现在该方法中。后面分析该方法

    2.- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder

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

    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];

    }

    和方法1 一样调用- (void)sd_setImageWithURL:(nullable NSURL *)url

    placeholderImage:(nullable UIImage *)placeholder

    options:(SDWebImageOptions)options

    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

    completed:(nullable SDExternalCompletionBlock)completedBlock

    方法,而该放的调用到- (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

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

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

    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];

    }

    调用同方法1 和方法2

    4- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock

    - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {

    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];

    }

    方法1 方法2 方法3 方法4 调用顺序一样,只不过是参数不同。

    5- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock

    - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {

    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];

    }

    方法5 同方法1 ,2 ,3 ,4 

    6 - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock

    - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {

    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];

    }

    方法6 同以上五个方法

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

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

    placeholderImage:(nullable UIImage *)placeholder

    options:(SDWebImageOptions)options

    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

    completed:(nullable SDExternalCompletionBlock)completedBlock {

    [self sd_internalSetImageWithURL:url

    placeholderImage:placeholder

    options:options

    operationKey:nil

    setImageBlock:nil

    progress:progressBlock

    completed:completedBlock];

    }

    该方法是上面6个方法调用的方法,没做任何处理。

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

    - (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url

    placeholderImage:(nullable UIImage *)placeholder

    options:(SDWebImageOptions)options

    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

    completed:(nullable SDExternalCompletionBlock)completedBlock {

    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];

    UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:key];

    [self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];

    }

    这个方法和上面八个方法稍微有点区别。没有具体看 SDWebImageManager SDImageCache 缓存的实现。从字面看是先找以前的图片,找不到就用占位图显示。(后面会分析这个类)

    9.- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray*)arrayOfURLs

    这个方法暂时不管,等先分析玩上面的代码再回来补充上

    10.- (void)sd_cancelCurrentAnimationImagesLoad;

    这个方法暂时不管,等先分析玩上面的代码再回来补充上

    这个类前面八个方法都调用到了UIView 的category(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

    那我们就先分析下UIView 的Category (webCache)类

    2.UIView+webCache

    这个类的外放的方法

    1.- (nullable NSURL *)sd_imageURL;

    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;

    3.- (void)sd_cancelCurrentImageLoad;

    4.- (void)sd_setShowActivityIndicatorView:(BOOL)show;

    5.- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;

    .6- (BOOL)sd_showActivityIndicatorView;

    7.- (void)sd_addActivityIndicator;

    8.- (void)sd_removeActivityIndicator;

    这个类public方法有八个。由于UIimageView(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;

    那我们先分析方法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 {    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);    [self sd_cancelImageLoadOperationWithKey:validOperationKey];    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) {        // check if activityView is enabled or not        if ([self sd_showActivityIndicatorView]) {            [self sd_addActivityIndicator];        }                __weak __typeof(self)wself = self;        idoperation = [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);

    }

    });

    }

    }

    方法比较长,先看方法传入的参数

    (nullable NSURL *)url  需要加载的URL

    (nullable UIImage *)placeholder  需要替代的image展位图,是UIImage类型的

    (SDWebImageOptions)options   

    (nullable NSString *)operationKey  

    (nullable SDSetImageBlock)setImageBlock  (设置图片block字面意思)

    (nullable SDWebImageDownloaderProgressBlock)progressBlock (进度条block)

    (nullable SDExternalCompletionBlock)completedBlock (完成block)

    这个方法传入参数有7个

    开始分析方法

    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);

    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

    第一句是判断key 

    第二句是调用UIview(WebCacheOperation) 的 - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key

    - (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 (idoperation in operations) {                if (operation) {                    [operation cancel];                }            }        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){            [(id) operations cancel];

    }

    [operationDictionary removeObjectForKey:key];

    }

    }

    这个方法调用

    - (SDOperationsDictionary *)operationDictionary {

    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);

    if (operations) {

    return operations;

    }

    operations = [NSMutableDictionary dictionary];

    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    return operations;

    }

    这个方法是找self 关联的NSMutableDictionary 如果没找到字典 就创建一个字典 ,将字典和self 通过关联引用绑定。

    看到这里我们就知道了,其实每个UIimageView 都有一个与之管理的Dictionary

    回到 函数- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key

    接着往下看代码

    id operations = operationDictionary[key];    if (operations) {        if ([operations isKindOfClass:[NSArray class]]) {            for (idoperation in operations) {                if (operation) {                    [operation cancel];                }            }        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){            [(id) operations cancel];

    }

    [operationDictionary removeObjectForKey:key];

    }

    从self 关联的dic 中获取key相关的值

    要是有值。那么判断 该值是不是数组。要是数组,那么该数组里面装的是SDWebImageOperation 协议的对象,那么就将该让该对象调用下cancel 方法。如果是SDWebImageOperation 协议对象,那么久直接调用cancel 方法。最后再移除self 关联dic中key 

    - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key函数分析完毕。那么返回- (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

    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    这个方法是给self 绑定url。那么我们知道self 绑定了有个dic  和 url

    if (!(options & SDWebImageDelayPlaceholder)) {

    dispatch_main_async_safe(^{

    [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];

    });

    }

    要是options参数没有配置 SDWebImageDelayPlaceholder那么就执行if里面的语句。

    dispatch_main_async_safe 是宏定义·

    #ifndef dispatch_main_async_safe

    #define dispatch_main_async_safe(block)\

    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\

    block();\

    } else {\

    dispatch_async(dispatch_get_main_queue(), block);\

    }

    #endif

    这里知道这个宏定义就是返回主线程执行block 。 这里的dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)是获取当前线程 的Label

    DISPATCH_CURRENT_QUEUE_LABEL  在queue.h 文件中这样写的

    /*!

    * @const DISPATCH_CURRENT_QUEUE_LABEL

    * @discussion Constant to pass to the dispatch_queue_get_label() function to

    * retrieve the label of the current queue.

    */

    #define DISPATCH_CURRENT_QUEUE_LABEL NULL

    要是不是延时加载Placeholder在主线程执行的函数

    - (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

    }

    这个函数

    1 第一步判断 setImageBlock 是不是nil 不是nil 那就调用setImageBlock 传入image 和imagedata 返回

    2.要是nil 那么就判断 self 是不是UIimageView  ,是UIimageView 就给imageView 赋值返回

    3要是self 是UIbutton 那么久给button 赋值

    我们再返回函数- (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

    接着往下看。

    if(url){

    《1》

    }else{

    《2》

    }

    下面就是对Url进行检测,如果有url 那么就进入《1》没有则进入《2》

    看《1》部分

    // check if activityView is enabled or not

    if ([self sd_showActivityIndicatorView]) {

    [self sd_addActivityIndicator];

    }

    - (BOOL)sd_showActivityIndicatorView {

    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];

    }

    看到这里我们知道了,self 还关联了一个activityIndicator是否显示的bool值。要是为yes 那么就调用- (void)sd_addActivityIndicator

    - (void)sd_addActivityIndicator {

    #if SD_UIKIT

    dispatch_main_async_safe(^{

    if (!self.activityIndicator) {

    self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];

    self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;

    [self addSubview:self.activityIndicator];

    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator

    attribute:NSLayoutAttributeCenterX

    relatedBy:NSLayoutRelationEqual

    toItem:self

    attribute:NSLayoutAttributeCenterX

    multiplier:1.0

    constant:0.0]];

    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator

    attribute:NSLayoutAttributeCenterY

    relatedBy:NSLayoutRelationEqual

    toItem:self

    attribute:NSLayoutAttributeCenterY

    multiplier:1.0

    constant:0.0]];

    }

    [self.activityIndicator startAnimating];

    });

    #endif

    }

    self.activityIndicator 调用下面方法

    - (UIActivityIndicatorView *)activityIndicator {

    return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);

    }

    self 上面还有关联引用一个UIActivityIndicatorView 

    self 上面目前关联里四个对象,一个dic ,url  ,UIActivityIndicatorView  ,和是否显示UIActivityIndicatorView 的bool值

    - (void)sd_addActivityIndicator 调用顺序

    1 检查self 是否关联一个UIActivityIndicatorView ,是,就开启动画。

    2.要是self 没有关联UIActivityIndicatorView,创建一个UIActivityIndicatorView (这里调用- (int)sd_getIndicatorStyle 方法, self又关联一个设置UIActivityIndicatorView样式的关联引用,五个了 )

    - (int)sd_getIndicatorStyle{

    return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];

    }

    3.将UIActivityIndicatorView 加入到self

    4 给UIActivityIndicatorView 增加约束,放入self 的中心位置

    5 开启动画

    再次返回函数《1》中的代码往下看

    __weak __typeof(self)wself = self;        idoperation = [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];

    这里面出现了一个新的类 SDWebImageManager 

    没办法,下面全是这个类的方法,只能分析SDWebImageManager  这个类了

    SDWebImageManager 调用+ (nonnull instancetype)sharedManager 方法

    + (nonnull instancetype)sharedManager {

    static dispatch_once_t once;

    static id instance;

    dispatch_once(&once, ^{

    instance = [self new];

    });

    return instance;

    }

    - (nonnull instancetype)init {

    SDImageCache *cache = [SDImageCache sharedImageCache];

    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];

    return [self initWithCache:cache downloader:downloader];

    }

    - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {

    if ((self = [super init])) {

    _imageCache = cache;

    _imageDownloader = downloader;

    _failedURLs = [NSMutableSet new];

    _runningOperations = [NSMutableArray new];

    }

    return self;

    }

    SDWebImageManager 是个单例类,在- (nonnull instancetype)init 中 又生产一个SDImageCacheSDWebImageDownloader 实例 直接调用- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader 方法,将 SDImageCache 和 SDWebImageDownloader 实例赋值给SDWebImageManager 的成员变量_imageCache _imageDownloader 。在这个类里面还生产 了_failedURLs(NSMutableSet)和_runningOperations(NSMutableArray)

    接下来看看 SDImageCache 生成方法。SDImageCache 调用+ (nonnull instancetype)sharedImageCache

    + (nonnull instancetype)sharedImageCache {

    static dispatch_once_t once;

    static id instance;

    dispatch_once(&once, ^{

    instance = [self new];

    });

    return instance;

    }

    SDImageCache 也是一个单例,继承NSObject

    - (instancetype)init {

    return [self initWithNamespace:@"default"];

    }

    init  方法调用 - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns 

    - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {

    NSString *path = [self makeDiskCachePath:ns];

    return [self initWithNamespace:ns diskCacheDirectory:path];

    }

    这个方法分两步

    1第一步生产一个path。路径是沙盒 cache路径下拼接传入的默认文件夹../cache/default

    - (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {    NSArray*paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

    return [paths[0] stringByAppendingPathComponent:fullNamespace];

    }

    2第二步调用- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns

    diskCacheDirectory:(nonnull NSString *)directory

    - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns

    diskCacheDirectory:(nonnull NSString *)directory {

    if ((self = [super init])) {

    NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

    // Create IO serial queue

    _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

    _config = [[SDImageCacheConfig alloc] init];

    // Init the memory cache

    _memCache = [[AutoPurgeCache alloc] init];

    _memCache.name = fullNamespace;

    // Init the disk cache

    if (directory != nil) {

    _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];

    } else {

    NSString *path = [self makeDiskCachePath:ns];

    _diskCachePath = path;

    }

    dispatch_sync(_ioQueue, ^{

    _fileManager = [NSFileManager new];

    });

    #if SD_UIKIT

    // Subscribe to app events

    [[NSNotificationCenter defaultCenter] addObserver:self

    selector:@selector(clearMemory)

    name:UIApplicationDidReceiveMemoryWarningNotification

    object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self

    selector:@selector(deleteOldFiles)

    name:UIApplicationWillTerminateNotification

    object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self

    selector:@selector(backgroundDeleteOldFiles)

    name:UIApplicationDidEnterBackgroundNotification

    object:nil];

    #endif

    }

    return self;

    }

    1这个第一步生成 fullNamespace 变量 值是com.hackemist.SDWebImageCache.default

    2.生产一个DISPATCH_QUEUE_SERIAL 串行queue,名字com.hackemist.SDWebImageCache,给变量_ioQueue

    3.生成一个 SDImageCacheConfig对象,赋值_config

    4.生成一个AutoPurgeCache 对象,赋值_memCache ,给_memCache赋值fullNamespace

    5.判断传入的路径,要是没有,那么就生成一个路径,赋值给_diskCachePath 变量,要是有,就直接拼接上拼接上fullNamespace,赋值给_diskCachePath 变量,(这里有默认路径)因此_diskCachePath路径是 ./cache/default/com.hackemist.SDWebImageCache.default

    6在_ioQueue 队列里生成NSFileManager对象赋值给_fileManager

    7.增加通知  内存警告 UIApplicationDidReceiveMemoryWarningNotification,程序结束UIApplicationWillTerminateNotification,进入后台UIApplicationDidEnterBackgroundNotification三个通知。

    这里看看 SDImageCacheConfig 初始化干嘛的

    - (instancetype)init {

    if (self = [super init]) {

    _shouldDecompressImages = YES;

    _shouldDisableiCloud = YES;

    _shouldCacheImagesInMemory = YES;

    _maxCacheAge = kDefaultCacheMaxCacheAge;

    _maxCacheSize = 0;

    }

    return self;

    }

    很简单就是给变量赋值。这里有个kDefaultCacheMaxCacheAge  最大缓存时间

    static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 缓存一个周

    再 看看 AutoPurgeCache 。AutoPurgeCache 继承NSCache 。

    - (nonnull instancetype)init {

    self = [super init];

    if (self) {

    #if SD_UIKIT

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

    #endif

    }

    return self;

    }

    这里就是一个通知 当内存警告的时候,缓存调用移除所有对象的操作。

    SDImageCache 初始化就是一些通知注册和简单配置。目前没啥难度

    再看看SDWebImageDownloader。

    + (nonnull instancetype)sharedDownloader {

    static dispatch_once_t once;

    static id instance;

    dispatch_once(&once, ^{

    instance = [self new];

    });

    return instance;

    }

    SDWebImageDownloader 也是一个单例,继承NSObject。

    - (nonnull instancetype)init {

    return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

    }

    调用- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration 传入参数是[NSURLSessionConfiguration defaultSessionConfiguration]

    - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {

    if ((self = [super init])) {

    _operationClass = [SDWebImageDownloaderOperation class];

    _shouldDecompressImages = YES;

    _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;

    _downloadQueue = [NSOperationQueue new];

    _downloadQueue.maxConcurrentOperationCount = 6;

    _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";

    _URLOperations = [NSMutableDictionary new];

    #ifdef SD_WEBP

    _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];

    #else

    _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];

    #endif

    _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);

    _downloadTimeout = 15.0;

    [self createNewSessionWithConfiguration:sessionConfiguration];

    }

    return self;

    }

    这里就是给变量赋值

    1 _operationClass 变量赋值为SDWebImageDownloaderOperation class

    2 _shouldDecompressImages =Yes

    3 _executionOrder = SDWebImageDownloaderFIFOExecutionOrder

    4_downloadQueue 赋值一个NSOperationQueue 最大maxConcurrentOperationCount数量是6

    5 _URLOperations 是个数组初始化

    6 _HTTPHeaders 请求赋值 这里要是定义SD_WEBP那么就可以下载webP的图片,别的是正常图片。我搜了工程这个宏没有定义,本身不支持webP 要是你下载图片有webP那么你定义下

    7 _barrierQueue 生产一个并发队列

    8_downloadTimeout = 15.0; 超时设置

    9 调用- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration 函数

    - (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {

    [self cancelAllDownloads];

    if (self.session) {

    [self.session invalidateAndCancel];

    }

    sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;

    /**

    *  Create the session for this task

    *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate

    *  method calls and completion handler calls.

    */

    self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration

    delegate:self

    delegateQueue:nil];

    }

    在这个函数里,

    1 调用 - (void)cancelAllDownloads 函数

    - (void)cancelAllDownloads {

    [self.downloadQueue cancelAllOperations];

    }

    调用 self.downloadQueue 取消operations

    2 要是有self.session 就取消session

    3给sessionConfiguration 配置超时时间

    4用sessionConfiguration创建一个NSURLSession ,赋值给self.session

    这里都是写配置。暂时没有启动网络下面就是网络部分下载了。

    返回《1》 处往下看 SDWebImageManager调用- (id)loadImageWithURL:(nullable NSURL *)url

    options:(SDWebImageOptions)options

    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

    completed:(nullable SDInternalCompletionBlock)completedBlock

    方法。

    这个函数有一百多行,分段贴代码

    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    必须要有completedBlock 否则断言

    if ([url isKindOfClass:NSString.class]) {

    url = [NSURL URLWithString:(NSString *)url];

    }

    if (![url isKindOfClass:NSURL.class]) {

    url = nil;

    }

    检查URL 如果是nsstring 就转换成NSURL 在检查是不是NSURL 不是就是nil 。

    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];

    __weak SDWebImageCombinedOperation *weakOperation = operation;

    生成一个 SDWebImageCombinedOperation ;

    BOOL isFailedUrl = NO;

    if (url) {

    @synchronized (self.failedURLs) {

    isFailedUrl = [self.failedURLs containsObject:url];

    }

    }

    检查 self.failedURLs 数组里面是否包含包含就返回YES

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {

    [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];

    return operation;

    }

    要是url 为nil 或者 要是下载失败数组里面包含这个url 并且没有配置的options 参数SDWebImageRetryFailed(失败重试)那么就调用- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation

    completion:(nullable SDInternalCompletionBlock)completionBlock

    error:(nullable NSError *)error

    url:(nullable NSURL *)url 

    这个暂时不看继续向下面看

    @synchronized (self.runningOperations) {

    [self.runningOperations addObject:operation];

    }

    数组runningOperations 添加operation 

    NSString *key = [self cacheKeyForURL:url];

    - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {

    if (!url) {

    return @"";

    }

    if (self.cacheKeyFilter) {

    return self.cacheKeyFilter(url);

    } else {

    return url.absoluteString;

    }

    }

    这里要是设置了self.cacheKeyFilter block  那么就调用下 self.cacheKeyFilter(url) 否则直接返回url 的字符串格式。cacheKeyFilter 属性是 SDWebImageCacheKeyFilterBlock

    typedef NSString * _Nullable (^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable url);

    接着 self.imageCache 调用- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock 方法

    - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {

    if (!key) {

    if (doneBlock) {

    doneBlock(nil, nil, SDImageCacheTypeNone);

    }

    return nil;

    }

    // First check the in-memory cache...

    UIImage *image = [self imageFromMemoryCacheForKey:key];

    if (image) {

    NSData *diskData = nil;

    if ([image isGIF]) {

    diskData = [self diskImageDataBySearchingAllPathsForKey:key];

    }

    if (doneBlock) {

    doneBlock(image, diskData, SDImageCacheTypeMemory);

    }

    return nil;

    }

    NSOperation *operation = [NSOperation new];

    dispatch_async(self.ioQueue, ^{

    if (operation.isCancelled) {

    // do not call the completion if cancelled

    return;

    }

    @autoreleasepool {

    NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];

    UIImage *diskImage = [self diskImageForKey:key];

    if (diskImage && self.config.shouldCacheImagesInMemory) {

    NSUInteger cost = SDCacheCostForImage(diskImage);

    [self.memCache setObject:diskImage forKey:key cost:cost];

    }

    if (doneBlock) {

    dispatch_async(dispatch_get_main_queue(), ^{

    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);

    });

    }

    }

    });

    return operation;

    }

    这个函数

    1检查key 不存在 ,再检查doneBlock 存在就执行下,返回nil

    2 从记忆缓存中找 key

    - (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {

    return [self.memCache objectForKey:key];

    }

    3 如果key找到image 再检查是不是gif图片,是gif图片 那么调用- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key 方法。获取 磁盘数据

    4new一个NSOperation

    5 切换到self.ioQueue 队列 执行 block

    6在 ioQueue队列中判断new 的 NSOperation 是否被取消掉了。取消掉了。就返回

    7 调用- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key

    8调用 - (nullable UIImage *)diskImageForKey:(nullable NSString *)key 

    9判断 diskImage 并且配置shouldCacheImagesInMemory 是否应该缓存到记忆内存中

    是就缓存到记忆内存。

    10 doneBlock 存在就返回主线程执行doneBlock

    上面调用这个- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key 函数

    - (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {    NSString *defaultPath = [self defaultCachePathForKey:key];    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;

    }

    1 调用- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key  

    - (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {

    return [self cachePathForKey:key inPath:self.diskCachePath];

    }

    - (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {

    NSString *filename = [self cachedFileNameForKey:key];

    return [path stringByAppendingPathComponent:filename];

    }

    - (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {

    const char *str = key.UTF8String;

    if (str == NULL) {

    str = "";

    }

    unsigned char r[CC_MD5_DIGEST_LENGTH];

    CC_MD5(str, (CC_LONG)strlen(str), r);

    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",

    r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],

    r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];

    return filename;

    }

    - (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key 调用 - (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path

    接着调用 - (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key

    在这个函数里面 对key 进行md5  要是 key 有后缀名,就在md5 字符串后面加上后缀名字。

    最后再 将文件名字拼接到path上

    - (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key 函数的结果是获取文件所在路径path

    2 调用path 获取文件 数据。要是获取到了数据就直接返回data

    3 没有获取到数据,就去掉path后缀名再次获取数据。要是获取到就返回data

    4 接着从自定义在self.customPaths的缓存路径找 。

    5要是没找到就返回nil

    这里有个customPaths 。这个哪里来的么

    - (void)addReadOnlyCachePath:(nonnull NSString *)path {

    if (!self.customPaths) {

    self.customPaths = [NSMutableArray new];

    }

    if (![self.customPaths containsObject:path]) {

    [self.customPaths addObject:path];

    }

    }

    这个函数给custom 赋值,不是init 初始化好的。

    因此这个函数- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key 就是从磁盘上找 nsdata

    再看看- (nullable UIImage *)diskImageForKey:(nullable NSString *)key

    - (nullable UIImage *)diskImageForKey:(nullable NSString *)key {

    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];

    if (data) {

    UIImage *image = [UIImage sd_imageWithData:data];

    image = [self scaledImageForKey:key image:image];

    if (self.config.shouldDecompressImages) {

    image = [UIImage decodedImageWithImage:image];

    }

    return image;

    } else {

    return nil;

    }

    }

    - (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key

    这个函数分析过了。先从磁盘找data 。找到data 

    调用+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data  

    这个函数第一步检查data 

    第二步调用+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data 函数。

    + (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {

    if (!data) {

    return SDImageFormatUndefined;

    }

    uint8_t c;

    [data getBytes:&c length:1];

    switch (c) {

    case 0xFF:

    return SDImageFormatJPEG;

    case 0x89:

    return SDImageFormatPNG;

    case 0x47:

    return SDImageFormatGIF;

    case 0x49:

    case 0x4D:

    return SDImageFormatTIFF;

    case 0x52:

    // R as RIFF for WEBP

    if (data.length < 12) {

    return SDImageFormatUndefined;

    }

    NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];

    if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {

    return SDImageFormatWebP;

    }

    }

    return SDImageFormatUndefined;

    }

    这个函数检测图片的头部分,根据头部分返回图片的格式。改后缀名没啥用。(后期分析各种图片的结构)

    第三步判断是不是gif图片。如果是gif图片,调用+ (UIImage *)sd_animatedGIFWithData:(NSData *)data函数对gif图片进行处理

    + (UIImage *)sd_animatedGIFWithData:(NSData *)data {

    if (!data) {

    return nil;

    }

    #if SD_MAC

    return [[UIImage alloc] initWithData:data];

    #endif

    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);

    size_t count = CGImageSourceGetCount(source);

    UIImage *staticImage;

    if (count <= 1) {

    staticImage = [[UIImage alloc] initWithData:data];

    } else {

    // we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.

    // this here is only code to allow drawing animated images as static ones

    #if SD_WATCH

    CGFloat scale = 1;

    scale = [WKInterfaceDevice currentDevice].screenScale;

    #elif SD_UIKIT

    CGFloat scale = 1;

    scale = [UIScreen mainScreen].scale;

    #endif

    CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);

    #if SD_UIKIT || SD_WATCH

    UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];

    staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];

    #endif

    CGImageRelease(CGImage);

    }

    CFRelease(source);

    return staticImage;

    }

    这个主要是判断是将gif 的data 处理成gif为一张静态 格式的image,判断是不是data为空,为空就返回。将data 转换成 CGImageSourceRef  获取 CGImageSourceRef 的图片数量。判断如果小于或者等于1张,那么就直接生成UIimage ,否则,就获取屏幕的scale,获取第一张CGImageRef 将CGImageRef 转换成image 返回这个image 。这个image就一张图片。

    看到这里有个sd_imageWithWebPData:函数,查找工程找不到,所以这里要是图片是webp 会崩溃。工程暂时不支持webP

    这里不是gif 或者webP 那么就生成图片,调用+(UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData函数,获取 图片的方向

    +(UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {

    UIImageOrientation result = UIImageOrientationUp;

    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);

    if (imageSource) {

    CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);

    if (properties) {

    CFTypeRef val;

    int exifOrientation;

    val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);

    if (val) {

    CFNumberGetValue(val, kCFNumberIntType, &exifOrientation);

    result = [self sd_exifOrientationToiOSOrientation:exifOrientation];

    } // else - if it's not set it remains at up

    CFRelease((CFTypeRef) properties);

    } else {

    //NSLog(@"NO PROPERTIES, FAIL");

    }

    CFRelease(imageSource);

    }

    return result;

    }

    + (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation {

    UIImageOrientation orientation = UIImageOrientationUp;

    switch (exifOrientation) {

    case 1:

    orientation = UIImageOrientationUp;

    break;

    case 3:

    orientation = UIImageOrientationDown;

    break;

    case 8:

    orientation = UIImageOrientationLeft;

    break;

    case 6:

    orientation = UIImageOrientationRight;

    break;

    case 2:

    orientation = UIImageOrientationUpMirrored;

    break;

    case 4:

    orientation = UIImageOrientationDownMirrored;

    break;

    case 5:

    orientation = UIImageOrientationLeftMirrored;

    break;

    case 7:

    orientation = UIImageOrientationRightMirrored;

    break;

    default:

    break;

    }

    return orientation;

    }

    获取 CGImageSourceRef 对象,获取该对象的属性CFDictionaryRef,从字典里获取kCGImagePropertyOrientation key 的值 ,调用函数+ (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation 将值转换成枚举值

    回到第四步中,接着判断 方向是不是向上的,不是向上,重新生成对应方向上的image

    (不过这里就有个疑问?难道通过[[UIImage alloc] initWithData:data]; 方式生成的图片,方向可能错误,非要进行这样的矫正?)

    返回到- (nullable UIImage *)diskImageForKey:(nullable NSString *)key 函数中,接着掉用函数- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image ,该函数调用C函数SDScaledImageForKey 

    - (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {

    return SDScaledImageForKey(key, image);

    }

    inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {    if (!image) {        return nil;    }    #if SD_MAC    return image;#elif SD_UIKIT || SD_WATCH    if ((image.images).count > 0) {        NSMutableArray*scaledImages = [NSMutableArray array];

    for (UIImage *tempImage in image.images) {

    [scaledImages addObject:SDScaledImageForKey(key, tempImage)];

    }

    UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];

    #ifdef SD_WEBP

    if (animatedImage) {

    SEL sd_webpLoopCount = NSSelectorFromString(@"sd_webpLoopCount");

    NSNumber *value = objc_getAssociatedObject(image, sd_webpLoopCount);

    NSInteger loopCount = value.integerValue;

    if (loopCount) {

    objc_setAssociatedObject(animatedImage, sd_webpLoopCount, @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    }

    #endif

    return animatedImage;

    } else {

    #if SD_WATCH

    if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) {

    #elif SD_UIKIT

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {

    #endif

    CGFloat scale = 1;

    if (key.length >= 8) {

    NSRange range = [key rangeOfString:@"@2x."];

    if (range.location != NSNotFound) {

    scale = 2.0;

    }

    range = [key rangeOfString:@"@3x."];

    if (range.location != NSNotFound) {

    scale = 3.0;

    }

    }

    UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];

    image = scaledImage;

    }

    return image;

    }

    #endif

    }

    看下SDScaledImageForKey函数。

    第一步 要是image 为nil 直接返回

    第二步检查 image的images 数组中的数量是不是大于0 ,大于0 说明是gif图。那么又重新生成了一遍gif图

    不是gif 图。那么检查屏幕像素scale,默认一 ,一旦传入的key 是@2x ,就变成2x图,@3x,变成3Scale图。

    接着返回看- (nullable UIImage *)diskImageForKey:(nullable NSString *)key函数

    接着检查 shouldDecompressImages 是否解压缩,如果yes 的话,+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image 调用

    + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {

    if (![UIImage shouldDecodeImage:image]) {

    return image;

    }

    // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.

    // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];

    @autoreleasepool{

    CGImageRef imageRef = image.CGImage;

    CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];

    size_t width = CGImageGetWidth(imageRef);

    size_t height = CGImageGetHeight(imageRef);

    size_t bytesPerRow = kBytesPerPixel * width;

    // kCGImageAlphaNone is not supported in CGBitmapContextCreate.

    // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast

    // to create bitmap graphics contexts without alpha info.

    CGContextRef context = CGBitmapContextCreate(NULL,

    width,

    height,

    kBitsPerComponent,

    bytesPerRow,

    colorspaceRef,

    kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);

    if (context == NULL) {

    return image;

    }

    // Draw the image into the context and retrieve the new bitmap image without alpha

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);

    CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);

    UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha

    scale:image.scale

    orientation:image.imageOrientation];

    CGContextRelease(context);

    CGImageRelease(imageRefWithoutAlpha);

    return imageWithoutAlpha;

    }

    }

    第一步检查image 是不是能加压缩

    + (BOOL)shouldDecodeImage:(nullable UIImage *)image {

    // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error

    if (image == nil) {

    return NO;

    }

    // do not decode animated images

    if (image.images != nil) {

    return NO;

    }

    CGImageRef imageRef = image.CGImage;

    CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);

    BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||

    alpha == kCGImageAlphaLast ||

    alpha == kCGImageAlphaPremultipliedFirst ||

    alpha == kCGImageAlphaPremultipliedLast);

    // do not decode images with alpha

    if (anyAlpha) {

    return NO;

    }

    return YES;

    }

    gif 或者是透明度是 kCGImageAlphaFirst kCGImageAlphaLast kCGImageAlphaPremultipliedFirst kCGImageAlphaPremultipliedLast 样式的图片不能解压缩

    第二步,要是能解压缩,获取CGImageRef 获取 CGColorSpaceRef

    + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {

    // current

    CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));

    CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);

    BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||

    imageColorSpaceModel == kCGColorSpaceModelMonochrome ||

    imageColorSpaceModel == kCGColorSpaceModelCMYK ||

    imageColorSpaceModel == kCGColorSpaceModelIndexed);

    if (unsupportedColorSpace) {

    colorspaceRef = CGColorSpaceCreateDeviceRGB();

    CFAutorelease(colorspaceRef);

    }

    return colorspaceRef;

    }

    这个是对image的颜色空间进行转换kCGColorSpaceModelMonochrome kCGColorSpaceModelUnknown kCGColorSpaceModelCMYK kCGColorSpaceModelIndexed 转换成RGB颜色空间

    第三步,获取 image 宽度 高度 bytesPerRow

    第四步,创建 context 

    第五步 CGContextDrawImage 绘制image

    第六步 获取CGImageRef

    第七步 将生成 image

    这个地方不难看懂,主要是怎么进行重新生成图片。这个用的是bitmap 。(为啥这么做,目前我也不知道有啥好处,后期研究图片的样式可能会解开这个问题)

    到目前为止- (nullable UIImage *)diskImageForKey:(nullable NSString *)key函数分析完毕了,就是对图片的各种处理。

    返回上级- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock,接着看,[self diskImageForKey:key] 往下看,下面判断shouldCacheImagesInMemory =Yes 的话,就将 diskImage 缓存到Cache 。再往下就好说了,判断回调block doneBlock 有的话,就返回主线程 执行 doneBlock

    下面看看doneBlock 执行 的是啥。

    返回函数- (id)loadImageWithURL:(nullable NSURL *)url

    options:(SDWebImageOptions)options

    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

    completed:(nullable SDInternalCompletionBlock)completedBlock  看doneBlock

    好长的block 。

    快接近文章上限了,分下章解析doneBlock。

    相关文章

      网友评论

        本文标题:SDWebImage源码分析

        本文链接:https://www.haomeiwen.com/subject/unrxpxtx.html