SDWebImage
Posted 驭狼共舞
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SDWebImage相关的知识,希望对你有一定的参考价值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef NS_ENUM(NSInteger, SDImageCacheType) { /** * The image wasn‘t available the SDWebImage caches, but was downloaded from the web. 该图像是不可用的SDWebImage缓存,但是从网络下载的. */ SDImageCacheTypeNone, /** * The image was obtained from the disk cache. 图像从磁盘高速缓存获得. */ SDImageCacheTypeDisk, /** * The image was obtained from the memory cache. 图像从存储器高速缓存获得 */ SDImageCacheTypeMemory }; |
SDWebImage是ios开发者经常使用的一个开源框架,这个框架的主要作用是:一个异步下载图片并且支持缓存的UIImageView分类。
UIImageView+WebCache
我们最常用的方法就是这个:
1
|
[_fineImageView sd_setImageWithURL:picURL placeholderImage:nil]; |
现在开始我们一步步地看这个方法的内部实现:
1
2
3
|
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } |
这里会调用下面这个方法:
1
2
3
4
|
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock |
我们看UIImageView+WebCache.h文件,我们可以发现为开发者提供了很多类似于- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder的方法。
这些方法最终都会调用- sd_setImageWithURL: placeholderImage: options: progress: completed:,这个方法可以算是核心方法,下面我们看一下- sd_setImageWithURL: placeholderImage: options: progress: completed:
方法的实现:
首先执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
//移除UIImageView当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView的下载和缓存组合操作都被取消 [self sd_cancelCurrentImageLoad]; 接下来我们来看看[self sd_cancelCurrentImageLoad]内部是怎么执行的: - (void)sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:@ "UIImageViewImageLoad" ]; } //然后会调用UIView+WebCacheOperation的 - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key UIView+WebCacheOperation 下面我们先来看看UIView+WebCacheOperation里面都写了些什么: UIView+WebCacheOperation这个分类提供了三个方法,用于操作绑定关系 #import #import "SDWebImageManager.h" @interface UIView (WebCacheOperation) /** * Set the image load operation (storage in a UIView based dictionary) 设置图像加载操作(存储在和UIView做绑定的字典里面) * * @param operation the operation * @param key key for storing the operation */ - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key; /** * Cancel all operations for the current UIView and key 用这个key找到当前UIView上面的所有操作并取消 * * @param key key for identifying the operations */ - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key; /** * Just remove the operations corresponding to the current UIView and key without cancelling them * * @param key key for identifying the operations */ - (void)sd_removeImageLoadOperationWithKey:(NSString *)key; |
为了方便管理和找到视图正在进行的一些操作,WebCacheOperation将每一个视图的实例和它正在进行的操作(下载和缓存的组合操作)绑定起来,实现操作和视图一一对应关系,以便可以随时拿到视图正在进行的操作,控制其取消等,如何进行绑定我们在下面分析:
UIView+WebCacheOperation.m文件内
- (NSMutableDictionary *)operationDictionary用到了中定义的两个函数:
-
objc_setAssociatedObject
-
objc_getAssociatedObject
NSObject+AssociatedObject.h
1
2
3
|
@interface NSObject (AssociatedObject) @property (nonatomic, strong) id associatedObject; @end |
NSObject+AssociatedObject.m
1
2
3
4
5
6
7
8
|
@implementation NSObject (AssociatedObject) @dynamic associatedObject; -(void)setAssociatedObject:(id)object{ objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -(id)associatedObject { return objc_getAssociatedObject(self, @selector(associatedObject)); } |
objc_setAssociatedObject作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。
objc_getAssociatedObject根据key获得与对象绑定的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
- (NSMutableDictionary *)operationDictionary { /* 这个loadOperationKey 的定义是:static char loadOperationKey; 它对应的绑定在UIView的属性是operationDictionary(NSMutableDictionary类型) operationDictionary的value是操作,key是针对不同类型视图和不同类型的操作设定的字符串 注意:&是一元运算符结果是右操作对象的地址(&loadOperationKey返回static char loadOperationKey的地址) */ NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); //如果可以查到operations,就rerun,反正给视图绑定一个新的,空的operations字典 if (operations) { return operations; } operations = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return operations; } - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key { // 取消正在下载的队列 NSMutableDictionary *operationDictionary = [self operationDictionary]; //如果 operationDictionary可以取到,根据key可以得到与视图相关的操作,取消他们,并根据key值,从operationDictionary里面删除这些操作 id operations = [operationDictionary objectForKey:key]; if (operations) { if ([operations isKindOfClass:[NSArray class]]) { for (id operation in operations) { if (operation) { [operation cancel]; } } } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ [(id) operations cancel]; } [operationDictionary removeObjectForKey:key]; } } |
接下来我们继续探索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- sd_setImageWithURL: placeholderImage: options: progress: completed: - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { [self sd_cancelCurrentImageLoad]; //将 url作为属性绑定到ImageView上,用static char imageURLKey作key objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); /*options & SDWebImageDelayPlaceholder这是一个位运算的与操作,!(options & SDWebImageDelayPlaceholder)的意思就是options参数不是SDWebImageDelayPlaceholder,就执行以下操作 #define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } */ |
这是一个宏定义,因为图像的绘制只能在主线程完成,所以dispatch_main_sync_safe就是为了保证block在主线程中执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ //设置imageView的placeHolder self.image = placeholder; }); } if (url) { // 检查是否通过`setShowActivityIndicatorView:`方法设置了显示正在加载指示器。如果设置了,使用`addActivityIndicator`方法向self添加指示器 if ([self showActivityIndicatorView]) { [self addActivityIndicator]; } __weak __typeof(self)wself = self; //下载的核心方法 id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { //移除加载指示器 [wself removeActivityIndicator]; //如果imageView不存在了就return停止操作 if (!wself) return ; dispatch_main_sync_safe(^{ if (!wself) return ; /* SDWebImageAvoidAutoSetImage,默认情况下图片会在下载完毕后自动添加给imageView,但是有些时候我们想在设置图片之前加一些图片的处理,就要下载成功后去手动设置图片了,不会执行`wself.image = image;`,而是直接执行完成回调,有用户自己决定如何处理。 */ if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return ; } /* 如果后两个条件中至少有一个不满足,那么就直接将image赋给当前的imageView ,并调用setNeedsLayout */ else if (image) { wself.image = image; [wself setNeedsLayout]; } else { /* image为空,并且设置了延迟设置占位图,会将占位图设置为最终的image,,并将其标记为需要重新布局。 */ if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; // 为UIImageView绑定新的操作,以为之前把ImageView的操作cancel了 [self sd_setImageLoadOperation:operation forKey:@ "UIImageViewImageLoad" ]; } else { // 判断url不存在,移除加载指示器,执行完成回调,传递错误信息。 dispatch_main_async_safe(^{ [self removeActivityIndicator]; if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @ "Trying to load a nil url" }]; completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } } |
SDWebImageManager
-sd_setImageWithURL:forState:placeholderImage:options:completed:中,下载图片方法是位于SDWebImageManager类中- downloadImageWithURL: options:progress:completed:函数
我们看下文档是对SDWebImageManager怎么描述的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
/** * The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. * It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). * You can use this class directly to benefit from web image downloading with caching in another context than * a UIView. * * Here is a simple example of how to use SDWebImageManager: * * @code /* 概述了SDWenImageManager的作用,其实UIImageVIew+WebCache这个Category背后执行操作的就是这个SDWebImageManager.它会绑定一个下载器也就是SDwebImageDownloader和一个缓存SDImageCache */ /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. 若图片不在cache中,就根据给定的URL下载图片,否则返回cache中的图片 * * @param url The URL to the image * @param options A mask to specify options to use for this request * @param progressBlock A block called while image is downloading * @param completedBlock A block called when operation has been completed. * * This parameter is required. * * This block has no return value and takes the requested UIImage as first parameter. * In case of error the image parameter is nil and the second parameter may contain an NSError. * * The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache * or from the memory cache or from the network. * * The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is * downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the * block is called a last time with the full image and the last parameter set to YES. * * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation */ /* *第一个参数是必须的,就是image的url *第二个参数options你可以定制化各种各样的操作 *第三个参数是一个回调block,用于图片下载过程中的回调 *第四个参数是一个下载完成的回调,会在图片下载完成后回调 *返回值是一个NSObject类,并且这个NSObject类是遵循一个协议这个协议叫SDWebImageOperation,这个协议里面只写了一个协议,就是一个cancel一个operation的协议 */ - (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock; |
我们继续看SDWebImageManager .m
-
初始化方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* *初始化方法 *1.获得一个SDImageCache的单例 *2.获得一个SDWebImageDownloader的单例 *3.新建一个MutableSet来存储下载失败的url *4.新建一个用来存储下载operation的可遍数组 */ - (id)init { if ((self = [ super init])) { _imageCache = [self createCache]; _imageDownloader = [SDWebImageDownloader sharedDownloader]; _failedURLs = [NSMutableSet new ]; _runningOperations = [NSMutableArray new ]; } return self; } |
利用image的url生成一个缓存时需要的key,cacheKeyFilter的定义如下:
1
2
|
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter; typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url); |
是一个可以返回一个字符串的block
1
2
3
4
5
6
7
8
9
10
|
//如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key //如果为空,那么直接返回URL的string内容,当做key. - (NSString *)cacheKeyForURL:(NSURL *)url { if (self.cacheKeyFilter) { |