技术无极限,从菜鸟开始,从源码开始。
由于公司目前项目还是用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 中 又生产一个SDImageCache和 SDWebImageDownloader 实例 直接调用- (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。
网友评论