SDWebImage源码分析

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

由于公司目前项目还是用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。

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

推荐阅读更多精彩内容