SDWebImage源码阅读笔记

阅读一下iOS开发中最常用的 OC 图片加载缓存库

说明

  1. SDWebImage地址: https://github.com/rs/SDWebImage
  2. 版本: 4.1.0
  3. 只分析 iOS OC 版本的相关实现, MacOS, watchOS等平台以及 Swift 版本暂不涉及, 代码实现大同小异

请求过程

以下面代码为例:

1
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://abc.com/1.png"] placeholderImage:[UIImage imageNamed:@"placeholderImage.png"]];

内部调用

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
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. 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 a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (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;

项目目录结构及其功能

  1. SDWebImageOperation : 是一个protocol, 就定义了一个cancle方法; SDWebImageCompat 类: 主要是一些不同系统平台下的常用宏定义
  2. Downloader : 下载类,主要是对NSOperation以及NSURLSessionTask以及相关操作
  3. Cache : 缓存类, 主要是SDImageCache以及SDImageCacheConfig,内部涉及memory cache 和 disk cache的处理
  4. Utils : 工具类, 主要是管理调用上层接口,以及一些解压图片的工具,外加一个图片预取工具类
  5. Categories : 项目中使用系统类的扩展方法
  6. WebCache Categories : 一些 UI 控件的扩展方法,一般是我们直接调用的接口

SDWebImageCompat

兼容各个平台系统的类。
TargetConditionals.h 这个头文件定义了系统常用的宏,比如 CPU 平台,编译器版本等。
__OBJC_GC__这个宏用来判断是否当前平台是否支持GC,诸如此类。
FOUNDATION_EXPORT等同于FOUNDATION_EXTERN,用来兼容C++,实际代码如下:

1
2
3
4
5
#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif

重新定义了NS_ENUM的枚举定义,兼容旧语法

1
2
3
4
5
6
7
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#ifndef NS_OPTIONS
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif

快速使用主线程回调的宏:通过dispatch_queue_get_label()获取当前线程的labelstrcmp()进行比较主线程的label,如果是主线程,返回;否则,异步回调到主线程,调用block

1
2
3
4
5
6
7
8
#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

根据key生成图片2x和3x的图片,key的生成之后可以看到是根据URL生成的
FOUNDATION_EXPORT UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
在这个方法的实现中,为了兼容WebP格式的图片,使用了objc_setAssociatedObject动态生成一个对象用于存储循环次数。
QQ音乐技术团队的微信号里面这篇文章介绍了WebP这种图片格式的优缺点,可以看一下。

Downloader

主要是两个类SDWebImageDownloaderSDWebImageDownloaderOperation

SDWebImageDownloader类的功能

包含下载策略的枚举:首先定义了SDWebImageDownloaderOptions,这是下载策略的定义,初始值使用了位移操作,默认是SDWebImageDownloaderUseNSURLCache,支持缓存。
SDWebImageDownloaderExecutionOrder这个枚举定义了下载队列的执行策略,分别为FIFO(队列,默认)和LIFO(栈)两种。
定义SDWebImageDownloadToken类,用于标记每一个下载任务,可以用于取消下载。

1
2
3
4
5
6
7
8
9
/**
* A token associated with each download. Can be used to cancel a download
*/
@interface SDWebImageDownloadToken : NSObject
@property (nonatomicstrongnullable) NSURL *url;
@property (nonatomicstrongnullable) id downloadOperationCancelToken;
@end

SDWebImageDownloader类的关键实现

下载器,是一个单例,继承自NSObject,遵循<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>协议,重点查看指定初始化方法
NS_DESIGNATED_INITIALIZER用来指明此方法是指定初始化方法。
+ (void)initialize;方法里面对下载开始结束进行监听,并对下载指示器SDNetworkActivityIndicator进行开启和关闭的操作。
初始化:

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
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
// 下载队列的类, 稍后会查看这个类的实现以及作用
_operationClass = [SDWebImageDownloaderOperation class];
// 默认解压图片
_shouldDecompressImages = YES;
// 下载队列,默认先进先出
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
// 初始化下载队列
_downloadQueue = [NSOperationQueue new];
// 最大下载数,默认6
_downloadQueue.maxConcurrentOperationCount = 6;
// 队列名称
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
// 用于存储下载的URL
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
// 设置请求头,默认接受所有格式的图片,权重默认0.8
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
// 并发队列类型的, 用于统一操作, 保证执行完当前操作之后才进行之后的操作, 可以查看相关用法
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
// 超时时间, 默认15秒
_downloadTimeout = 15.0;
// 配置下载任务,新建一个NSURLSession,设置当前对象为代理,delegateQueue为nil
[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}

关于项目开发中的GCD实战应用有写_barrierQueue的一般用法。
支持方法:
核心方法,下载操作,代码大致如下:

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
// 添加下载进度,主要是根据URL创建一个下载队列
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
...
// 定义一个下载任务的token
__block SDWebImageDownloadToken *token = nil;
// 在barrierQueue
dispatch_barrier_sync(self.barrierQueue, ^{
// 根据URL取出下载任务,不存在则创建一个
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
// 根据URL生成下载队列,如果一张图片会被多次下载,那么这个队列也只会创建一次
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
// 移除已经下载成功的URL
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
});
};
}
// 设置下载进度,返回一个字典,记录下载的状态
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// 生成token
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
// 下载任务
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
...
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
// 注释说的很明显,这样可以防止系统重复缓存同一张图片
NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
if (options & SDWebImageDownloaderUseNSURLCache) {
if (options & SDWebImageDownloaderIgnoreCachedResponse) {
cachePolicy = NSURLRequestReturnCacheDataDontLoad;
} else {
cachePolicy = NSURLRequestUseProtocolCachePolicy;
}
}
// 生成一个网络请求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
// HTTP管道,默认为NO,设置YES,可以允许前一个请求服务器未返回结果的时候重新发起新的请求
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
// 生成SDWebImageDownloaderOperation类型的下载任务
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
// 设置证书
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
// 设置队列中操作的优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
// 将操作添加到下载队列
[sself.downloadQueue addOperation:operation];
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
// 栈模式, 添加依赖,后进先出,所以顺序添加进来的操作,前一个操作依赖新添加进来的操作
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}

取消和暂停下载操作,主要是对下载队列进行操作,原生的NSOperationQueue是支持这些操作的,只是API的包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
dispatch_barrier_async(self.barrierQueue, ^{
// 根据请求的URL取出对应的下载operation
SDWebImageDownloaderOperation *operation = self.URLOperations[token.url];
// 调用operation 的 cancle方法
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
// 在字典中移除已经取消的operation
[self.URLOperations removeObjectForKey:token.url];
}
});
}
- (void)setSuspended:(BOOL)suspended {
self.downloadQueue.suspended = suspended;
}
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}

SDWebImageDownloaderOperation类

SDWebImageDownloaderOperationInterface是一个protocol,定义了SDWebImageDownloaderOperation的一些重要方法,包括初始化方法以及添加进度的方法
SDWebImageDownloaderOperation的声明,

1
2
// 继承自 NSOperation
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

其中后两个协议SDWebImageDownloader也实现了,并且回调中只是把回调传递给operation类,即真实的NSURLSession 的 Delegate方法实际是在operation类中进行回调处理,下载器只是一层回调包装。
查看这个类中的实现,初始化方法中生成一些默认属性以及一个队列

1
2
3
4
5
6
7
// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
// the task associated with this operation
// 初始化中的session,使用这个弱引用保存
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
// 强引用的session需要手动销毁
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;

SDWebImageDownloaderOperationInterface中的一个方法引人注目:

1
2
3
4
5
6
7
8
9
10
11
12
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 一个字典,用户保存执行状态,并按照顺序添加到队列中
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
// 异步顺序添加到队列中,所有状态都保存在self.callbackBlocks中
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}

关注开始,取消,重置,完成等方法。
完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)done {
// 调用set方法
self.finished = YES;
self.executing = NO;
// 调用重置方法
[self reset];
}
// 手动调用kvo,在下载类中进行监听
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}

重置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)reset {
__weak typeof(self) weakSelf = self;
dispatch_barrier_async(self.barrierQueue, ^{
// 移除所有状态
[weakSelf.callbackBlocks removeAllObjects];
});
// 置空操作
self.dataTask = nil;
self.imageData = nil;
if (self.ownedSession) {
// 销毁session,立即取消所有任务
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}

取消:

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
// SDWebImageDownloaderOperationInterface协议方法
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
// 移除所有token类型的对象,即保存下载状态的字典
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
- (void)cancel {
// 同步锁,实际调用cancelInternal
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
// 调用父类方法
[super cancel];
if (self.dataTask) {
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}

重写了start方法:

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
- (void)start {
// 同步锁
@synchronized (self) {
// 如果被标记为结束
if (self.isCancelled) {
// 任务结束
self.finished = YES;
// 重置
[self reset];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
// 是否进入后台工作
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
// 后台任务
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
// 结束任务,防止崩溃
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
// 后台ID置为无效
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
// 获取当前任务
NSURLSession *session = self.unownedSession;
if (!self.unownedSession) {
// 不存在则创建
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* 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.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
// 手动调用kvo
self.executing = YES;
}
// 开始任务
[self.dataTask resume];
if (self.dataTask) {
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// 在主线程中发通知,下载任务开始
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
// 异常
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#if SD_UIKIT
// 再次判断程序是否存活
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}

关于并发属性,已经废弃使用concurrent,需要重写NSOperation的方法,可以用asynchronous代替

1
2
3
4
5
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
- (BOOL)isConcurrent {
return YES;
}

NSURLSessionDataDelegate方法
接收到服务器响应

1
2
3
4
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

首先状态码判断,无状态码或者是400以下并且不是304错误,根据response取出expectedContentLength,并赋值给属性expectedSize,设置对应请求的数据总长度,创建存储imageDate的可变对象,在主线程发送接收到服务器返回的通知。
否则,取消任务,并且发通知下载停止。

//‘304 Not Modified’ is an exceptional one

接收到数据

1
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

这一步进行数据的拼接self.options & SDWebImageDownloaderProgressiveDownload,并回调下载进度。options默认为0,只有设置过SDWebImageDownloaderProgressiveDownload模式,才会进条件,执行图片的创建,以及解压缩操作。
创建图片的方法:

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
// 创建一个image source ref,option 设置为空,所有option
// CFStringRef kCGImageSourceTypeIdentifierHint;
// CFStringRef kCGImageSourceShouldAllowFloat;
// CFStringRef kCGImageSourceShouldCache;
// CFStringRef kCGImageSourceCreateThumbnailFromImageIfAbsent;
// CFStringRef kCGImageSourceCreateThumbnailFromImageAlways;
// CFStringRef kCGImageSourceThumbnailMaxPixelSize;
// CFStringRef kCGImageSourceCreateThumbnailWithTransform
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
// ... 判断图片的方向以及高度和宽度,保存图片的方向
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
// 创建一个 image ref,设置为空,不做缓存操作
/*
CGContextRef CGBitmapContextCreate (
   void *data,
   size_t width,
   size_t height,
   size_t bitsPerComponent, // 内存中像素的每个组件的位数.例如,对于32位像素格式和RGB 颜色空间,你应该将这个值设为8.
   size_t bytesPerRow, // bitmap的每一行在内存所占的比特数
   CGColorSpaceRef colorspace, // 颜色空间
   CGBitmapInfo bitmapInfo // 信息,比如通道,或者像素排布的方式
);
*/
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
// iPhone上,图片的颜色空间使用rgb,其他的可以自行了解
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 创建一个bitmap上下文,32位颜色,设置为8位(r,g,b,a每个为4b),所以需要宽度 * 4
// kCGImageAlphaPremultipliedFirst // premultiplied ARGB
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext) {
// 绘制
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
// 根据上下文创建
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else {
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
// 进行缩放
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
UIImage *scaledImage = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
// 图片解压
image = [UIImage decodedImageWithImage:scaledImage];
} else {
image = scaledImage;
}

系统即将缓存

1
2
3
4
5
6
7
8
9
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// 置空
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}

关于证书的处理,具体参考代码实现

1
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

Cache

使用SDWebimage的原因,一是支持并发请求,二是支持图片缓存,下面看一下作者是如何构建一个图片下载库的缓存系统的。
Cache文件夹下主要有两个类

SDImageCacheConfig,缓存配置类

这个类简单一些,主要用于配置下载缓存的配置,比如缓存大小(0),缓存时间(默认一周),是否使用内存缓存(YES),是否禁用iCloud(默认禁用),是否解压图片(默认解压)

SDImageCache,缓存类

是个单例,继承自NSObject,属性中有memCache,用于内存缓存,diskCachePath,记录文件存储的文件夹,ioQueue,用于文件读写的队列,还有配置类,maxMemoryCostmaxMemoryCountLimit等设置项,customPaths用于存储那些预取的图片存储路径。
缓存使用内存缓存+文件缓存的策略。
内存缓存使用 NSCache实现,文件缓存是将图片URL进行处理(MD5)生成唯一文件名,然后存储在磁盘上。
创建,指定初始化方法

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
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
// 默认后缀是 default
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
// 初始化订阅的通知
// UIApplicationDidReceiveMemoryWarningNotification
// UIApplicationWillTerminateNotification
// UIApplicationDidEnterBackgroundNotification
#endif
}
return self;
}

分别看图片的操作。
存储,核心方法如下

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
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
... // 空值处理
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
// 根据图片尺寸计算 cost
NSUInteger cost = SDCacheCostForImage(image);
// 添加内存缓存
[self.memCache setObject:image forKey:key cost:cost];
}
// 图片解压缩,磁盘缓存是耗时操作,在 ioQueue 操作,防止阻塞主线程
if (toDisk) {
dispatch_async(self.ioQueue, ^{
// 大量对象生成释放,使用autoreleasepool控制内存
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// 图片二进制数据不存在,重新生成,首先获取图片的格式,然后使用`UIImagePNGRepresentation`或者`UIImageJPEGRepresentation`生成图片,之后会查看NSData的这些分类方法
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
data = [image sd_imageDataAsFormat:imageFormatFromData];
}
// 磁盘缓存核心方法
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程回调
completionBlock();
});
}
});
} else {
... // 回调
}
}
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
... // 空值操作
// 检测当前线程,核心方法
// const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
// 获取当前队列的label与ioQueue的label进行比较
[self checkIfQueueIsIOQueue];
// 创建缓存文件的文件夹
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
// 写入磁盘
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
if (self.config.shouldDisableiCloud) {
// iCloud同步文件
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}

查询,核心方法

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
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
...//空值处理
// First check the in-memory cache...
// 查询内存缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
// isGIF单独处理
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
// 查询完成,是内存缓存,并未在io线程操作
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
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
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
// 清除内存缓存
if (self.config.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
// 文件删除
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}
}
// 清除全部内存缓存
- (void)clearMemory {
[self.memCache removeAllObjects];
}
// 清除全部磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
// 创建空文件夹,方便覆盖以及下次缓存文件
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}

除了上面的核心方法,这个类还提供了一些工具方法,以及一些辅助方法。比如获取缓存文件个数,缓存大小,删除失效的缓存文件,内存警告的时候自动清除缓存,实现原来大概如下:子类化一个NSCache,AutoPurgeCache,在初始化方法中添加UIApplicationDidReceiveMemoryWarningNotification观察,当收到系统的内存警告,调用removeAllObjects清理掉缓存。

Utils

辅助类,主要是一些工具方法

SDWebImageManager,重要的调度类

前两部分的缓存类,下载器类,除了个别API,一般不需要使用者直接调用,而是在这个类中进行使用的。这个类主要包括SDWebImageCombinedOperation,一个SDWebImageManagerDelegateSDWebImageManager的单例对象。
SDWebImageCombinedOperation 遵循SDWebImageOperation协议,用于取消下载操作,实现方法里面有置空Block的操作。有一个cacheOperation的属性,用于存储需要cancle的操作。
SDWebImageManagerDelegate 主要定义了两个方法,一个是控制是否下载缓存中没有的图片,一个是缓存图片之前的调用方法,为防止主线程阻塞,回调是在一个全局队列。
SDWebImageManager的主要构成:

1
2
3
4
5
6
7
8
9
10
@interface SDWebImageManager : NSObject
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
// 缓存类
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
// 下载器
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
// 下载失败的URLs
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
// 保存正在下载的操作
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;

指定初始化方法是必须初始化缓存以及下载器。

1
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader;

主要方法:

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
170
171
172
173
174
175
176
/**
* Allows to specify instance of cache and image downloader used with image manager.
* @return new instance of `SDWebImageManager` with specified cache and downloader.
*/
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
/**
* 加载图片核心方法
*/
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
... url 的格式处理
// 创建一个对象用于保存加载图片的操作
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
// 是否是下载失败的图片,默认下载失败会重新下载
isFailedUrl = [self.failedURLs containsObject:url];
}
}
// 链接无效
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;
}
// 保存正在下载的操作
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
// 获取key,根据URL转成string,提供block可以自定义生成key的方法
NSString *key = [self cacheKeyForURL:url];
// 首先缓存中查询
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
// 移除取消的操作
[self safelyRemoveOperationFromRunning:operation];
return;
}
// 无缓存,进行下载
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
// 刷新
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
SDWebImageDownloaderOptions downloaderOptions = 0;
...各种图片加载策略的匹配
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
// subOperationToken保存有下载的操作,以及下载的URL
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
} else if (error) {
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
// 下载失败
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
// 下载失败的URL,先移除掉,以免真的无法下载还一直尝试下载
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
// 下载成功并且有缓存之前的操作,在高优先级的队列中进行操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//SDWebImageManagerDelegate的第二个方法
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
// 下载成功,回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
// 完成,移除操作
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
// 缓存中查询出来的图片
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// Image not in cache and download disallowed by delegate
// 缓存中无图片,且SDWebImageManagerDelegate设置不允许下载图片
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
/**
* 取消当前下载
*/
- (void)cancelAll {
// 互斥锁
...
// makeObjectsPerformSelector,让数组中的对象都执行某个方法
[copiedOperations makeObjectsPerformSelector:@selector(cancel)];
...
}
/**
* 加互斥锁
* 是否有下载,通过判断 runningOperation.count > 0
*/
- (BOOL)isRunning;
// 下面三个方法只是cache类的方法包装
/**
* 写入缓存
*/
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
/**
* 异步查询缓存,回调在主线程
*/
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* 异步查询磁盘缓存
*/
- (void)diskImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

SDWebImageDecoder:UIImage(ForceDecode)

是UIImage的一个category。提供一个图片解码的方法,以及一个图片缩放并解码的方法。从缓存中取出来的png或者其他格式的图片,需要转换成位图再进行显示,将其他格式的图片转换为位图,即为解码。通常来说,解码是比较耗时的。上面以及把重绘图片CGBitmapContextCreate方法解释过了。
下面这篇文章介绍了iOS中图片的解压缩过程。
谈谈 iOS 中图片的解压缩

SDWebImagePrefetcher

预先下载图片可以使用这个类,没什么好看的,内部也是调用SDWebImageManager对象的loadImageWithURL方法。

Categories

一些类的category方法,主要是用于组织代码,比较独立,和SDWebImageDecoder相似,跟SDWebImage的核心实现关联不大。

WebCache Categories

主要的调用接口层。平时加载button或者image view上的图片,都是通过这层接口调用实现的。
也是通过category的方式,给这些类增加方法,具体调用都是调用UIView+WebCache.h中的这个方法。

1
2
3
4
5
6
7
- (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;

总结

本文只是粗略的通读了源码,并未进行深入研究, 难免有疏漏,之后会重新阅读,整理一些阅读源码的收获。大概会从图片加载的流程,多线程管理,缓存设计,接口设计,以及代码组织方面进行分析。之后希望能阅读一下相关功能的其他实现库,做个对比。