0%

SDWebImage

SDWebImage介绍

  • 提供 UIImageView, UIButton, MKAnnotationView 的分类,用来加载网络图片,并进行缓存管理;

  • 异步方式来下载网络图片

  • 异步方式: memory (内存)+ disk (磁盘) 来缓存网络图片,自动管理缓存;

  • 后台图片解码,转换及压缩;

    • 空间换时间https://www.cocoanetics.com/2011/10/avoiding-image-decompression-sickness/
  • 同一个 URL 不会重复下载;

  • 失效的 URL 不会被无限重试;

  • 支持 GIF动画 及 WebP 格式;

  • 开启 子线程 进行耗时操作,不阻塞主线程;

下载完图片的储存发生在SDWebImageManager里面(下载operation也是这里面创建的),可以作为提前缓存图片的方式

SDWebImageContext

SDWebImageContextSetImageOperationKey

  1. 简单来说,这个key对应的value用于指定当前的id保存在NSMapTable中的key,后续的cancel、remove都需要通过这个key来找到对应的线程。
  2. 当指定同一个key加载图片时会先cancel之前存在的线程。
  3. SDWebImageContextSetImageOperationKey并不是在所有地方使用都生效的。

比如UIButton的sd_setImageWithURL:和sd_setBackgroundImageWithURL:系列方法。

在其内部需要通过这个这值来保存、区分不同状态(UIControlState)的图片加载线程,所以即使设置了SDWebImageContextSetImageOperationKey也会被覆盖:

SDWebImageContextCustomManager

可以传入一个自定义的SDWebImageManager,默认使用[SDWebImageManager sharedManager]

SDWebImageContextImageTransformer

可以传入一个id类型用于转换处理加载出来的图片。

  1. 在SDWebImageManager中也可以设置一个id默认为nil,但是只有SDWebImageContext没有配置SDWebImageContextImageTransformer,才会使用它。
    也就是配置优先级 SDWebImageContext>SDWebImageManager
  2. 如果设置了id不会缓存原始图片,只缓存处理后的图片。
  3. 对于同个图片、不同参数的id会被认为是不同的图片:会产生不同的缓存文件、会重复下载。

SDWebImageContextImageScaleFactor

在NSData -> UIImage时对图片放大比例,是个大于1的CGFloat值,默认值:

SDWebImageContextStoreCacheType

定义图片缓存规则具体看 SDImageCacheType中的定义。

SDWebImageContextDownloadRequestModifier

可以传入一个id,用于在加载图片前修改NSURLRequest。

  1. SDWebImageContextDownloadRequestModifier协议比较简单,只需要实现一个方法,返回一个修改后的NSURLRequest即可:
  2. 内建了一个SDWebImageDownloaderRequestModifier对象,可以使用Block方便的修改NSURLRequest

SDWebImageContextCacheKeyFilter

可以传入一个id,指定图片的缓存key。

  1. SDWebImageCacheKeyFilter协议也比较简单,只需要实现一个方法,返回一个对应的缓存key字符串即可
  • (nullable NSString *)cacheKeyForURL:(nonnull NSURL *)url;
  1. 内建了一个SDWebImageCacheKeyFilter对象,可以使用Block方便的返回缓存key

SDWebImageContextCacheSerializer

  1. 可以传入一个id,转换需要缓存的图片格式。
  2. 在SDWebImageManager中也可以设置一个id默认为nil,但是只有SDWebImageContext没有配置SDWebImageContextCacheSerializer,才会使用它。
    也就是配置优先级 SDWebImageContext>SDWebImageManager
  3. 通常用于需要缓存的图片格式与下载的图片格式不相符的时候,如:下载的时候为了节约流量、减少下载时间使用了WebP格式,但是如果缓存也用WebP,每次从缓存中取图片都需要经过一次解压缩,这样是比较影响性能的,就可以使用id,实现其中的协议方法:

SDWebImageContextLoaderCachedImage

可以传入一个UIImage的缓存图像。

  1. 这个值比较特殊,它是定义在SDImageLoader.m中的。
  2. 这个值可以认为是SDWebImage是内部用来从SDWebImageManager向SDWebImageDownloader(id)传递缓存图像的,自定义实现SDImageLoader协议可能会用到这个值,其他情况一般不会用到。
  3. 这个属性只有在 SDWebImageOptions包含SDWebImageRefreshCached策略时才生效,也就是他是SDWebImageRefreshCached这个策略的配套值。
  4. SDWebImageRefreshCached这个策略用于那些图片URL是静态的(图片更新时URL是不变的,SD给的例子是 Facebook graph api profile pics),这个时候它会根据HTTP header的 cache-control 字段来控制缓存并且使用NSURLCache来缓存图片,SDWebImageDownloader(id)中判断SDWebImageContextLoaderCachedImage存在并且策略是SDWebImageRefreshCached的情况,仍然会发起请求。

SDWebImageDownloader

  • self.URLOperations 包含所有的下载队列

这句话很重要 url和callback关联dic
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.

downloadOperationCancelToken 返回绑定当前operation的callback、progressBlock, 是一个可变字典

operation.loaderOperation = SDWebImageDownloadToken

  • addHandlersForProgress

其实是向 SDWebImageDownloaderOperation 里面的callbackBlocks注册回调callback

SDWebImageDownloadToken 其实包含

  • url
  • request
  • downloadOperationCancelToken (这是一个dic,又包含)
    • callback
    • progressBlock

cancel的时候

其中一个队列SDWebImageDownloaderOperation,包含所有url对应的回调

  • self.callbackBlocks

options参数:

SDWebImageOptions属性 说明
SDWebImageRetryFailed 默认情况下,如果一个url在下载的时候失败了,那么这个url会被加入黑名单并且library不会尝试再次下载,这个flag会阻止library把失败的url加入黑名单
SDWebImageLowPriorit 默认情况下,图片会在交互发生的时候下载(例如你滑动tableview的时候),这个flag会禁止这个特性,导致的结果就是在scrollview减速的时候,才会开始下载
SDWebImageProgressiveLoad 这个flag启动渐进式下载图像,类似浏览器加载图像那样逐步显示,默认情况下,图像仅是在下载完成后显示
SDWebImageRefreshCached 一个图片缓存了,还是会重新请求.并且缓存侧略依据NSURLCache而不是SDWebImage。即在URL没变但是服务器图片发生更新时使用
SDWebImageContinueInBackground 启动后台下载,实现原理是通过向系统询问后台的额外时间来完成请求的。 如果后台任务到期,则操作将被取消。
SDWebImageHandleCookies 当设置了NSMutableURLRequest.HTTPShouldHandleCookies = YES时,可以控制存储NSHTTPCookieStorage中的cookie
SDWebImageAllowInvalidSSLCertificates 允许不安全的SSL证书,用于测试环境,在正式环境中谨慎使用
SDWebImageHighPriority 默认情况下,image在装载的时候是按照他们在队列中的顺序装载的(就是先进先出)。这个flag会把他们移动到队列的前端,并且立刻装载,而不是等到当前队列装载的时候再装载。
SDWebImageDelayPlaceholder 默认情况下,占位图会在图片下载的时候显示.这个flag开启会延迟占位图显示的时间,等到图片下载完成之后才会显示占位图.
SDWebImageTransformAnimatedImage 通常不会在可动画的图像上调用 transformDownloadedImage 代理方法,因为大多数转换代码会破坏动画文件,这个flag为尝试转换
SDWebImageAvoidAutoSetImage 图片在下载后被加载到imageView。但是在一些情况下,我们想要设置一下图片(引用一个滤镜或者加入透入动画)这个flag来手动的设置图片在下载图片成功后
SDWebImageScaleDownLargeImages 默认情况下,图像将根据其原始大小进行解码。 在iOS上,此flat会将图片缩小到与设备的受限内存兼容的大小。 但如果设置了SDWebImageAvoidDecodeImage则此flat不起作用。 如果设置了SDWebImageProgressiveLoad它将被忽略。
SDWebImageQueryMemoryData 默认情况下,当图像已缓存在内存中时,我们不会查询图像数据。 此flat则强制查询图像数据。 此查询是异步的,除非指定SDWebImageQueryMemoryDataSync
SDWebImageQueryMemoryDataSync 结合SDWebImageQueryMemoryData设置同步查询图像数据(一般不建议这么使用,除非是在同一个runloop里避免单元格复用时发生闪现)
SDWebImageQueryDiskDataSync 如果内存查询没有的时候,强制同步磁盘查询(这三个查询可以组合使用,一般不建议这么使用,除非是在同一个runloop里避免单元格复用时发生闪现)
SDWebImageFromCacheOnly 默认情况下,当缓存丢失时,SD将从网络下载图像。 此flat可以防止这样,使其仅从缓存加载。
SDWebImageFromLoaderOnly 默认情况下,SD在下载之前先从缓存中查找,此flat可以防止这样,使其仅从网络下载
SDWebImageForceTransition 默认情况下,SD在图像加载完成后使用SDWebImageTransition进行某些视图转换,此转换仅适用于从网络下载图像。 此flat可以强制为内存和磁盘缓存应用视图转换。
SDWebImageAvoidDecodeImage 默认情况下,SD在查询缓存和从网络下载时会在后台解码图像,这有助于提高性能,因为在屏幕上渲染图像时,需要首先对其进行解码。这发生在Core Animation的主队列中。然而此过程也可能会增加内存使用量。 如果由于过多的内存消耗而遇到问题,可以用此flat禁止解码图像。
SDWebImageDecodeFirstFrameOnly 默认情况下,SD会解码动画图像,该flat强制只解码第一帧并生成静态图。
SDWebImagePreloadAllFrames 默认情况下,对于SDAnimatedImage,SD会在渲染过程中解码动画图像帧以减少内存使用量。 但是用户可以指定将所有帧预加载到内存中,以便在大量imageView共享动画图像时降低CPU使用率。这实际上会在后台队列中触发preloadAllAnimatedImageFrames(仅限磁盘缓存和下载)。

SDImageTransformer的类型

SDImageCache

NSCache

  • 自动删除机制:当系统内存紧张时,NSCache会自动删除一些缓存对象
  • 线程安全:从不同线程中对同一个 NSCache 对象进行增删改查时,不需要加锁
  • 不同于 NSMutableDictionary,NSCache存储对象时不会对 key 进行 copy 操作

小Tip

  1. 运行时存取关联对象:
  2. 数组的写操作需要加锁(多线程访问,避免覆写)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //给self.runningOperations加锁
    //self.runningOperations数组的添加操作
    @synchronized (self.runningOperations) {
    [self.runningOperations addObject:operation];
    }

    //self.runningOperations数组的删除操作
    - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
    if (operation) {
    [self.runningOperations removeObject:operation];
    }
    }
    }
  3. 确保在主线程的宏:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    dispatch_main_async_safe(^{
    //将下面这段代码放在主线程中
    [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
    });

    //宏定义:
    #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
  4. 设置不能为nil的参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    - (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;
    }
  5. 容错,强制转换类型
    1
    2
    3
    if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
    }

UIImageView分类方法,分流到了UIView的分类方法,注释如下

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
使用图片的 URL 和可选的 placeholder image 设置 imageView 的 image

下载是异步和缓存的

@param url image的URL
@param placeholder 初始图像,直至image数据请求完成
@param options image下载的时候的选择项-SDWebImageOptions,下方有详细介绍
@param context 为了补充options枚举没有的选择想,下方有详细介绍
@param setImageBlock 用于自定义设置image
@param progressBlock 在图片下载ing状态调用,注:在后台队列上执行
@param completedBlock 在操作完成后调用,返回类型为
completedBlock(image, data, error, cacheType, finished, url)

*/

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
//将可变对象copy为不可变的
context = [context copy]; // copy to avoid mutable object

//取出context设置中的operationKey
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//如果operationKey为nil,就采用他自身的类的字符串作为key
if (!validOperationKey) {
validOperationKey = NSStringFromClass([self class]);
}
//取消之前绑定的operation,保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
//为自身绑定一个URL
self.sd_imageURL = url;
//如果不是延迟显示placeholder的情况
if (!(options & SDWebImageDelayPlaceholder)) {
//dispatch_main_async_safe 异步线程安全,下面有源码介绍
dispatch_main_async_safe(^{
//在图片下载下来之前,添加临时的占位图
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}

if (url) {
// reset the progress
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;

#if SD_UIKIT || SD_MAC
// check and start image indicator
// 检查是否设置了image indicator,如果有则启动,下面有方法的源码
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
// 判断context中是否自定义了manager,如果没有则使用默认的
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}

// 设置image加载进度Block(已接收size,预计总size,image的URL)
__weak __typeof(self)wself = self;
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
// 使用__strong __typeof是防止self在这个执行过程中释放,下方有详细介绍
__strong __typeof (wself) sself = wself;
NSProgress *imageProgress = sself.sd_imageProgress;
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
#if SD_UIKIT || SD_MAC
//加载指示器是否实现了updateIndicatorProgress方法
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = imageProgress.fractionCompleted;
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
#endif //返回image加载进度Block
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
//下载图片
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
if (!sself) { return; }

// 如果progress没有更新,则标记其为完成状态
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
//const int64_t SDWebImageProgressUnitCountUnknown = 1LL; (LL是 long long 类型的缩写)
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}

#if SD_UIKIT || SD_MAC
// 检查并停止 image indicator
if (finished) {
[self sd_stopImageIndicator];
}
#endif
// 下载完成后是否自动加载图片 (options & SDWebImageAvoidAutoSetImage)根据枚举名取枚举中的值,get了
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
// 设置completedBlock
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, data, error, cacheType, finished, url);
}
};

// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
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
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}

#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
// 检查image的过渡动画效果
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
#endif
// 设置image的过渡动画效果
dispatch_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 cacheType:cacheType imageURL:imageURL];
#endif
callCompletedBlockClojure();
});
}];
//在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行,源码见下
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// 没有url,停止Image Indicator
#if SD_UIKIT || SD_MAC
[self sd_stopImageIndicator];
#endif
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
}
希望对您有所帮助,您的支持将是我莫大的动力!