SDWebImage整体的框架:
1 | SDWebImage |
1、使用配置
为项目添加一个通用的只读缓存的存储路径:
1 | NSString *bundlePath = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:@"CustomPathImages"]; |
2、 身份鉴定
如果请求的图片需要身份鉴定才可以获取,SDWebImage提供了两种方法:
一种是直接设置用户名和密码:
1 | [SDWebImageManager sharedManager].imageDownloader.username = @"httpwatch"; |
另一种是通过 NSURLCredential
属性去配置用户名和密码
1 | NSURLCredential *newCredential = [NSURLCredential credentialWithUser:@"httpwatch" password:@"httpwatch01" persistence:NSURLCredentialPersistenceNone]; |
这里做下测试:
图片地址(需要身份验证才可以查看):http://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?0.35786508303135633
浏览器去打开会弹出提示框让输入验证信息,如下:
这种情况下,在项目中就需要配置身份验证信息,否则图片无法显示。
3、设置要附加到每个下载HTTP请求的HTTP标头的值
如果想要自定义图片请求的Request Header时,可以使用以下方法:
1 | //设置要附加到每个下载HTTP请求的HTTP标头的值。 |
通过charles抓包可以看到设置的Request Header:
4、图片下载执行顺序
执行顺序分:LIFO(先入后出) 和 FIFO(先进先出)两种
默认值为 FIFO
。
SDWebImageDownloaderExecutionOrder
:枚举类型
1 | SDWebImageManager.sharedManager.imageDownloader.executionOrder = SDWebImageDownloaderLIFOExecutionOrder; |
5、手动清理缓存
SDWebImage提供了手动清理缓存操作,从SDWebImage的工作原理可以想到清理缓存需清理两处:内存和磁盘。
注:这里清理的仅仅是SDWebImage的缓存,并没有清理整个app的缓存
1 | [SDWebImageManager.sharedManager.imageCache clearMemory]; |
6、将图片显示到ImgaeView / Button上
Button图片或者背景图的显示和ImageView同理,只是多了几个额外的参数,下面以ImageView为例。
6.1、图片显示动画
图片显示过渡类型:1
self.customImageView.sd_imageTransition = SDWebImageTransition.curlUpTransition;
SDWebImage提供了七种动画过渡类型,分别是:
1 | /// Fade transition. 淡出过渡 |
6.2 、图片显示方法
SDWebImage提供7种方法去显示图片,我们只看第7个,因为其他6种方法最后都是通过该方法图显示图片的。
1 | - (void)sd_setImageWithURL:(nullable NSURL *)url |
通过给定的URL加载图片并将其加载到此imageView中。它适用于静态和动态图像。
先从缓存和磁盘中寻找该图片,有则直接显示,无就下载显示并缓存。
下载是异步和缓存的,SDWebImage会先显示传入的占位符,直到请求完成
6.2.1分析参数
- @param url :图片URL
- @param placeholder :占位符(最初要设置的图像,直到图像请求完成。)
- @param options :下载图像时使用的选项。
- @param progressBlock :下载图像时调用的block代码块(稍后分析)
- @param completedBlock : 操作完成时调用的代码块。 该代码块没有返回值(稍后分析)
解析 options 选项(枚举类型)
SDWebImageRetryFailed = 1 << 0,
默认情况下当通过URL下载图片失败后,该URL就被加入黑名单,之后SDWebImage不会再去尝试下载。此标志作用就是禁用该黑名单,也就是说使用SDWebImageRetryFailed后,图片下载失败仍会尝试下载
SDWebImageLowPriority = 1 << 1,
默认情况下图片在UI交互期间下载,此标志的作用就是禁用该功能。例如:在UIScrollView减速时导致延迟下载。
SDWebImageCacheMemoryOnly = 1 << 2,
此标志作用是:图片下载完成后仅缓存到内存,不缓存在磁盘上
SDWebImageProgressiveDownload = 1 << 3,
此标志启用渐进式下载,图像在下载过程中逐步显示,就像浏览器一样。
默认情况下,图像仅在完全下载后显示。
SDWebImageRefreshCached = 1 << 4,
即使缓存中存在该图片,也尊重HTTP响应缓存控制,在需要时从远程刷新图片。
磁盘缓存将由NSURLCache
替代SDWebImage
去处理,这样也将导致性能略有下降。
该选项有助于处理同一个URL请求但更换图片的情况,例如Facebook图形api配置文件的图片。
如果刷新了缓存图片,则使用缓存图片调用一次完成block代码块,再使用最终的图片调用完成block代码块。
仅当你无法使用嵌入式缓存清除参数使URL保持静态时,才使用该标志。
SDWebImageContinueInBackground = 1 << 5,
在iOS 4+
中,如果app进入后台也继续下载图片,这是通过询问系统实现的。
在后台有额外的时间让请求完成,如果后台任务到期,则操作被取消。
SDWebImageHandleCookies = 1 << 6,
通过NSMutableURLRequest.HTTPShouldHandleCookies = YES
设置处理在NSHTTPCookieStore
中的cookies。
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
启用允许不受信任的SSL证书(用于测试目的,在生产中谨慎使用)
SDWebImageHighPriority = 1 << 8,
默认情况下,图像按其排队顺序加载,此标志将它们移动到队列的前面
SDWebImageDelayPlaceholder = 1 << 9,
默认情况下,加载图像时会加载占位符。 此标志将延迟加载占位符图像,直到图像加载完毕。
SDWebImageTransformAnimatedImage = 1 << 10,
我们通常不会在动画图像上调用transformDownloadedImage委托方法,因为大多数转换代码会破坏它。如果使用必须使用此标志来转换它们。
SDWebImageAvoidAutoSetImage = 1 << 11,
默认情况下,下载后会将图像添加到imageView。 但在某些情况下,我们想要
在设置图像之前手动处理一些东西(例如应用滤镜或添加交叉渐变动画)请使用此标志
SDWebImageScaleDownLargeImages = 1 << 12,
默认情况下,图像会根据其原始大小进行解码。 在iOS上,此标志将缩小
图像尺寸与设备的受限内存兼容。(如果设置了“SDWebImageProgressiveDownload”标志,则停用缩小比例。)
SDWebImageQueryDataWhenInMemory = 1 << 13,
默认情况下,当图像缓存在内存中时,我们不查询磁盘数据。 此掩码可以强制同时查询磁盘数据。
建议将此标志与SDWebImageQueryDiskSync
一起使用,以确保图像在同一个runloop中加载。
SDWebImageQueryDiskSync = 1 << 14,
默认情况下,我们同步查询内存缓存,异步查询磁盘缓存。 此掩码可以强制同步查询磁盘缓存,以确保在同一个runloop中加载映像。
如果禁用内存缓存或在某些其他情况下,此标志可以避免在单元重用期间闪烁。
SDWebImageFromCacheOnly = 1 << 15,
默认情况下,当缓存丢失时,将从网络下载映像。此标志可以阻止网络仅从缓存加载。
SDWebImageForceTransition = 1 << 16
默认情况下,当您使用SDWebImageTransition
在图像加载完成后进行某些视图转换时,此转换仅适用于从网络下载图像。 此掩码也可以强制为内存和磁盘缓存应用视图转换。
解析progressBlock代码块
下载图像时重复调用的代码块,在后台队列上执行,包含三个参数,分别是:
NSInteger receivedSize:接收到的图片大小
NSInteger expectedSize:预期的图片大小
NSURL * _Nullable targetURL:目标图片的URL
completedBlock代码块
包含四个参数:
(UIImage _Nullable image, NSError _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL)
- 第一个参数是:请求得到的UIImage,如果请求失败,该图像参数是为nil
- 第二个参数: 请求结果中可能包含的NSError
- 第三个参数: 是枚举,缓存类型,通过类型判断图片从哪里获取。分三种: SDImageCacheTypeNone 、SDImageCacheTypeDisk、SDImageCacheTypeMemory 。
- 第四个参数:图片的URL。
以下是其他6种显示图片的方法,只做简单的分析: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/**
*通过给定的URL加载图片并将其加载到此imageView中。它适用于静态和动态图像
* 先从缓存和磁盘中寻找,有则显示,无就下载显示并缓存
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder ;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options ;
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock ;
- (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
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
7、图像显示的最终调用方法解析
通过上面的七种方法来加载图像,其实前6种都是调用第7种完善的方法。而这7种方法都是调用的另一种方法。如下:
1 | - (void)sd_internalSetImageWithURL:(nullable NSURL *)url |
参数说明:
- @param url :图像URL
- @param placeholder :初始化image,知道图像请求完成
- @param options :图像下载时的选项
- @param operationKey :用作操作key的字符串。 如果为nil,将使用类名(ImageView加载图像时直接传的nil,Button加载图像时传【字符串和UIControlState状态的拼接】)
- @param setImageBlock : 用于自定义设置图像的代码块
- @param progressBlock : 下载图像时调用的代码块
- @param completedBlock : 操作完成时调用的代码块。 该块没有返回值
- @param context : 具有执行特定更改或过程的额外信息的上下文。
其实可以看出:除了参数4、5、8之外,剩下的都是和ImageView加载图像时参数一样,因为这本就是ImageView加载图像最终调用的方法。而参数4、5、8则是Button加载图像传入的参数,这也证明了代码的高内聚力。
SDSetImageBlock:
包含两个参数:
(UIImage image, NSData imageData)
我们通常在该回调中自定义图像(比如说压缩、剪切、加滤镜、加蒙层等)
或者直接简单的显示图像:[weakSelf setImage:image forState:state];
看下整体的代码:1
2
3
4
5
6
7
8
9[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:imageOperationKeyForState(state)
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setImage:image forState:state]; //自定义图像
}
progress:nil
completed:completedBlock];
context : 上下文信息
这个是最后才加上的,为的是获取额外的信息(加载的过程信息、更改信息等)
7.1、分析最终的加载图像的方法
该方法是针对UIView的子类进行图像加载的(例如:UIImageView、UIButton)
下面会拆成代码片段一步一步看
7.1.1、第一步
1 | static char imageURLKey; |
- 初始化一个有效的操作key,如果传入的
operationKey
为nil,则赋值为类名NSStringFromClass([self class])
- 通过
operationKey
从队列中取消正在进行的下载操作。 - 最后将该类与图像的URL关联起来,关联类型是 :
OBJC_ASSOCIATION_RETAIN_NONATOMIC
(指定对关联对象的强引用。)
以下列出所有的关联类型
关联策略 | 等价属性 | 说明 |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) or @property (unsafe_unretained) | 弱引用关联对象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (strong, nonatomic) | 强引用关联对象,且为非原子操作 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (copy, nonatomic) | 复制关联对象,且为非原子操作 |
OBJC_ASSOCIATION_RETAIN | @property (strong, atomic) | 强引用关联对象,且为原子操作 |
OBJC_ASSOCIATION_COPY | @property (copy, atomic) | 复制关联对象,且为原子操作 |
再回到第2条看下怎么通过 operationKey
取消对应正在进行下载的操作(顺便附上加载图像的操作,因为两者正好始和末):
代码: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//设置加载图像的操作
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
//设置取消加载图像的操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
分析:
首先,获取当前的一个 NSMapTable
,可以说是一个广义的字典,官方文档是这样解释 NSMapTable
的:
An NSMapTable is modeled after a dictionary, although, because of its options, is not a dictionary because it will behave differently. The major option is to have keys and/or values held “weakly” in a manner that entries will be removed at some indefinite point after one of the objects is reclaimed. In addition to being held weakly, keys or values may be copied on input or may use pointer identity for equality and hashing.
An NSMapTable can also be configured to operate on arbitrary pointers and not just objects.We recommend the C function API for “void *” access
大体可以理解为:
NSMapTable
是在字典之后的一个可变集合模型化的类,但由于它的选项是使 key
和/或 valus
保持“弱有化”,以便在回收其中一个对象之后在某个不确定点删除条目,所以它不是字典,因为它的行为会有所不同。 除了被弱化之外,可以在输入上复制 key
或 valus
,或者可以使用指针标识来进行相等和 hash
(散列or哈希)操作。
NSMapTable
也可以配置为对任意指针进行操作,而不仅仅是对象。 Apple建议使用C函数API进行 void *
访问。
SDWebImage是这样定义 NSMapTable
的:
1
2// key is copy, value is weak because operation instance is retained by SDWebImageManager's runningOperations property
typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
key使用的是copy,value则使用的是weak,并给出了解释:操作实例由SDWebImageManager的runningOperations属性保留
注:获取当前的 NSMapTable
,以下用 operationDictionary
叙述。
其次,将该图像的加载操作存放入 operationDictionary
中,而key正是我们上面分析的 operationKey
。这里为了防止在操作是被篡改,使用 @synchronized
做了互斥锁处理。
最后,现在再看取消加载图像的操作流程就显得清晰了,大体如下:
- 获取当前的一个
operationDictionary
通过key取得操作。 - 如果该操作存在,就通过
conformsToProtocol:@protocol()
检查对象是否实现了指定协议类的方法。 - 如果存在就直接取消,最后从移除
operationDictionary
该操作
扩展:objc_setAssociatedObject
- 在 Objective-C 中可以通过 Category 给一个现有的类添加属性,却不能添加实例变量,这成了 Objective-C 的一个明显短板。但可以通过 Associated Objects 来弥补这一不足。
- 相关函数:
与 Associated Objects 相关的函数主要有三个:1
2
3void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
在使用是需要引入 objc/runtime.h
头文件
- 函数的作用:
objc_setAssociatedObject 用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象;
objc_getAssociatedObject 用于获取关联对象;
objc_removeAssociatedObjects 用于移除一个对象的所有关联对象。 - key值使用:
推荐的方法大体有三种:
(1)声明 static char kAssociatedObjectKey; 使用 &kAssociatedObjectKey 作为 key 值;
(2)声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; 使用 kAssociatedObjectKey 作为 key 值;
(3)用 selector ,使用 getter 方法的名称作为 key 值。
我们看到的SDWebInage是使用的 static char kAssociatedObjectKey
这种方法。而 static char
这种声明是C语言的写法,意思是:声明一个局部静态变量。
附上一个讲的很不错的文章:Objective-C Associated Objects 的实现原理
7.1.2、第二步
代码片段:1
2
3
4
5if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
解析:
条件语句判断options选项是否为 SDWebImageDelayPlaceholder
若不是则执行条件语句里的内容。(条件语句中执行的是设置占位符图像,但该选项是延迟占位符加载,所以需要加以判断。)
dispatch_main_async_safe(^{})
宏定义主线程异步安全加载,如下:
1 | #ifndef dispatch_main_async_safe |
dispatch_main_async_safe
安全的分发任务到主线程里面运行dispatch_queue_get_label
用来取队列的名字,进而判断如果当前已经是主队列,那么直接执行,否则回调到主队列之后再执行。
原因是:如果当前队列已经是主队列,那么再调用 dispatch_async(dispatch_get_main_queue(), block)
有可能会出现crash
。而该方法则很好的做了预防工作。
下面看条件语句中的执行方法:1
2
3
4
5
6
7
8
9
10
11
12
13- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
#if SD_UIKIT || SD_MAC
[self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:0 imageURL:nil];
#else
// watchOS does not support view transition. Simplify the logic
if (setImageBlock) {
setImageBlock(image, imageData);
} else if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
[imageView setImage:image];
}
#endif
}
该方法目的为了执行加载占位符图像。分了两种情况 iOS和VTOS 、masOS系统
和 其他。分类型的原因 SDWebImage
给了简单说明: watchOS
不支持 view
的翻转动画,所以 SDWebImage
做了一个简单的加载显示处理。而其他类型则和图像URL的加载显示共用了一套方法(具体实现在加载URL图像时细看)。1
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL
7.1.3、第三步 正式准备加载URL图像
同样分两种情况,根据所传的 url
是否存在做区分
url
不存在情况1
2
3
4
5
6
7
8
9dispatch_main_async_safe(^{
#if SD_UIKIT
[self sd_removeActivityIndicator];
#endif
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
分析:
首先确保在线程安全的情况下,如果iOS和VTOS系统
就做下移除加载指示器操作。
如果completedBlock
代码块存在,就获取错误信息,执行回调内的操作。SDExternalCompletionBlock
的声明:1
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
下面是url传nil时的情况:
url
存在情况
- 1、如果是
iOS和VTOS
的系统,则判断使用显示加载指示器,如果显示则创建并添加到View上。1
2
3
4
5
6#if SD_UIKIT
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
#endif
配置加载指示器:1
2[self.customImageView sd_setShowActivityIndicatorView:YES];
[self.customImageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];
- 2、根据上下文获取
SDWebImageManager
根据上下文(context
)获取 SDWebImageManager
,没有则创建。1
2
3
4
5
6SDWebImageManager *manager;
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
} else {
manager = [SDWebImageManager sharedManager];
}
SDWebImageManager
是 SDWebImage
管理以及操作的类。该类是SDWebImage
的核心类,拥有一个SDWebImageCache 和 SDWebImageDownloader 属性,分别用于图片缓存和下载处理。
关于 SDWebImageManager
这里不细说,分出去单独研究。
- 3 加载进度回调(或者说加载进度回调中…)
下载图像时重复调用该代码块,在后台队列上执行。1
2
3
4
5
6
7
8
9
10
11
12// reset the progress
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
__weak __typeof(self)wself = self;
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
代码块中会判断传入的 progressBlock
是否为空,不为空的话执行回调操作,回调方法中开发者可以处理一些其他操作。看下图:
7.1.4、第四步 加载URL图像
加载图像的代码比较多,接下来就在代码中加注释分析。
加载图像的方法是在 SDWebImageManager
类中完成的,这里只是执行加载完成后的代码块中的内容。1
2
3
4- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock
加载图像的过程先不去考虑,先看加载完成后的代码块中执行的内容: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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself; //代码块中创建强引用
if (!sself) { return; } //如果强引用的self不存在,退出block
#if SD_UIKIT
[sself sd_removeActivityIndicator]; //不是iOS and tvOS系统的话,移除加载指示器
#endif
// if the progress not been updated, mark it to complete state (如果进度未更新,请将其标记为完成状态)
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
//如果加载结束、没错误、sd_imageProgress.totalUnitCount 和 sd_imageProgress.completedUnitCount 都为0,则设置两者的值为未知值
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
是否应该回调CompletedBlock ( 加载完成 或者 传入的 options 选项 为 SDWebImageAvoidAutoSetImage)
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//判断是否应该不设置Image (如果有图片但设置了SDWebImageAvoidAutoSetImage 或者没有图片并且没有设置SDWebImageDelayPlaceholder)
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
//如果view不存在,终止执行
if (!sself) { return; }
需要设置Image,就刷新视图
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
//如果传入了completedBlock并且应该回调,则执行回调
if (completedBlock && shouldCallCompletedBlock) {
//回调
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set (我们得到了一个图像,但设置了SDWebImageAvoidAutoSetImage标志)
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set(我们没有图像,并且没有设置SDWebImageDelayPlaceholder标志)
//如果不需要设置图片就在主线程队列种调用上面生成的完成回调代码块,然后停止执行
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
//初始化变量
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set (我们得到了一个图像,并且没有设置SDWebImageAvoidAutoSetImage)
//如果图片下载成功就将其保存到变量中
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set(我们没有图像,并且设置了SDWebImage Delay Placeholder标志)
如果图片下载失败并且设置了延迟加载占位符图像,就保存占位符图像
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC (iOS and tvOS macOS)
// check whether we should use the image transition(检查我们是否应该使用图像过渡转换)
SDWebImageTransition *transition = nil;
//如果加载结束并且options选项为:SDWebImageForceTransition 或者 缓存类型为:SDImageCacheTypeNone
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
//保存图像过渡转换
transition = sself.sd_imageTransition;
}
#endif
dispatch_main_async_safe(^{
//主线程队列种设置图像
#if SD_UIKIT || SD_MAC (iOS and tvOS macOS)
设置图像
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
//iWatchOS系统设置图像
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
#endif
//设置完成后调用完成回调代码块
callCompletedBlockClojure();
});
}];
//SDOperationsDictionary存储当前的操作。
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
}
下面再使用纯描述的方式,过一遍流程:(为了简便,以下:SD_UIKIT || SD_MAC
简称:iOS_tvOS_MAC
; SD_MAC
简称:MAC
SD_UIKIT
简称 :iOS_tvOS
)
__strong
在Block
内部修饰的对象,会保证,在使用这个对象在block内,这个对象都不会被释放。
之前分析过,加载指示器只能在iOS_tvOS
的情况下添加,所以加载完成后需要移除。如下:1
2
3#if SD_UIKIT
[sself sd_removeActivityIndicator];
#endif
如果加载已经完成并且没有错误;iamge进度的总单元和完成单元都是0,就设置imageProgress
的总单元和完成单元值为未知。
创建 “应该执行完成加载的回调” 和 “不应该设置Image” 两个布尔值。SDWebImageAvoidAutoSetImage
: 在设置图像之前手动处理一些东西(例如应用滤镜或添加交叉渐变动画)请使用此标志SDWebImageDelayPlaceholder
: 延迟显示占位符图像
1 | //加载完成 或者 传入的 options 选项为 SDWebImageAvoidAutoSetImage |
SDWebImageNoParamsBlock
回调1
2
3
4
5
6
7
8
9
10
11SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
//需要设置Image
[sself sd_setNeedsLayout];
}
//completedBlock存在并且需要回调
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
不需要设置Image,执行callCompletedBlockClojure回调代码1
2
3
4if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
图像和图像Data赋值:1
2
3
4
5
6
7
8
9
10
11UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
如果系统类型为:iOS_tvOS_MAC
,图像过渡动画赋值1
2
3
4
5
6
7#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
#endif
执行异步安全加载,根据不同的系统执行不同的方法。1
2
3
4
5
6
7
8
9dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
#endif
callCompletedBlockClojure();
});
}];
以上就是大体加载图像所展示的流程
接下来看一下需要动画翻转过渡的代码(可以使用动画过渡的是iOS_tvOS_MAC
)
其实就是多加了一个转换动画的执行代码。其他的就是UIImageView和UIButton 的图像设置。
1 | #if SD_UIKIT || SD_MAC |
以上就是 SDWebImage
设置图片的整个流程,但只是过了一遍代码而已,内部的实现并没有深入,例如:SDWebImage
的下载、缓存机制。这些都没有深入去了解。