一、SDWebImage到底是什么
Asynchronous image downloader with cache support with an UIImageView category
一个异步下载图片并且支持缓存的 UIImageView 分类
二、源码解读
最近花了一天的时间学习SDWebImage的源码,结果受益匪浅。废话不多说了直接看源码解读。
首先是SDWebImage的层次结构,分清主次,找到核心的类和方法,其他的都是为这个类或者方法做辅助,就和中药学当中的一剂良药分为君臣佐使一个道理。
先看看这个框架里面都有哪些类:
图示我框起来的四个类我个人认为的比较重要的,主要围绕着几个讲,在讲这几个之前,先把这些里面用到辅助类进行一番说明
1、主要辅助类
① NSData+ImageContentType
头文件
#import "SDWebImageCompat.h"
typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatUndefined = -1,
SDImageFormatJPEG = 0,
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP,
SDImageFormatHEIC
};
@interface NSData (ImageContentType)
/**
* Return image format
*
* @param data the input image data
*
* @return the image format as `SDImageFormat` (enum)
*/
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;
/**
Convert SDImageFormat to UTType
@param format Format as SDImageFormat
@return The UTType as CFStringRef
*/
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format;
@end
这个类里面就两个方法
第一个方法:顾名思义:判断一张图片类型,给我传一个图片的二进制数据,返回这张图片的类型
第二个方法:顾名思义:将SDImageFormat图片格式转换成UTType类型(C语言样式的字符串)
实现文件
// Currently Image/IO does not support WebP
#define kSDUTTypeWebP ((__bridge CFStringRef)@"public.webp")
// AVFileTypeHEIC is defined in AVFoundation via iOS 11, we use this without import AVFoundation
#define kSDUTTypeHEIC ((__bridge CFStringRef)@"public.heic")
@implementation NSData (ImageContentType)
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
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: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
}
break;
}
}
return SDImageFormatUndefined;
}
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format {
CFStringRef UTType;
switch (format) {
case SDImageFormatJPEG:
UTType = kUTTypeJPEG;
break;
case SDImageFormatPNG:
UTType = kUTTypePNG;
break;
case SDImageFormatGIF:
UTType = kUTTypeGIF;
break;
case SDImageFormatTIFF:
UTType = kUTTypeTIFF;
break;
case SDImageFormatWebP:
UTType = kSDUTTypeWebP;
break;
case SDImageFormatHEIC:
UTType = kSDUTTypeHEIC;
break;
default:
// default is kUTTypePNG
UTType = kUTTypePNG;
break;
}
return UTType;
}
第一个方法实现原理就是先判断参数的合法性,然后取出图片二进制数据第一个字节进行判断,根据结果返回相对应的图片格式
第二个方法根据SDImageFormat不同值返回对应的UTType类型
以上就是NSData+ImageContentType全部内容,最简单了,接着看
② SDWebImageCompat
声明了一系列的宏以及根据设备平台的不同分别导入系统库头文件,还有就是提供了一个可供外界调用用于设置图片scale的内联方法的声明、声明了一个无参block、用于主线程中执行方法调用比较重要的方法以及宏定义
// 内联方法的声明
extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
// 无参block
typedef void(^SDWebImageNoParamsBlock)();
// 主线程执行block
#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
③ SDWebImageDecoder
工程中用到的图片都是经过解压缩过的图片,通常使用图片都逃不过下面所示步骤
UIImage *image = [UIImage imageNamed:@"custom_icon"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
imageView.image = [UIImage imageWithCGImage:newImage];
[self.view addSubview:imageView];
虽是几个简单的步骤,但是系统CPU确实做了很多工作:
可能会涉及以下部分或全部步骤:
1)分配内存缓冲区用于管理文件 IO 和解压缩操作;
2)将文件数据从磁盘读到内存中;
3)将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;
4)最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。
图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。
为什么要强制解压缩??
是否可以不经过解压缩,而直接将图片显示到屏幕上呢?答案是否定的。
这是因为在将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因。
而SDWebImageDecoder这个类的作用就是强制解压缩,这个单开一篇介绍图片解压缩,最新的SDWenImage源码解压缩文件结构如下:
④ SDWebImageCombinedOperation
将实现了SDWebImageOperation协议的NSObject子类包装成一个看着像NSOperation其实并不是NSOperation的类,而这个类唯一与NSOperation的相同之处就是它们都可以响应cancel方法,这里能够响应cancel方法,也是其属性cacheOperation(NSOperation类)响应cancel方法。
@interface SDWebImageCombinedOperation : NSObject
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
@synchronized(self) {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
self.cancelBlock = nil;
}
}
}
@end
2、UIView+WebCache
平时在项目开发中,用的最多的就是以下情况了吧
[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]];
进入到方法实现发现原来这个方法的最终实现是在UIView+WebCache.h文件中的sd_internalSetImageWithURL:placeholderImage:options:operationKey:setImageBlock:progress:completed:方法中
- (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 {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (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];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed: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];
}
- (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];
}
- (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];
}
- (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
context:(nullable NSDictionary *)context {
//#1
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 使当前UIView中的所有操作都被cancel.不会影响之后进行的下载操作.
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
//#2
// 当前UIView绑定下载图片链接NSURL地址
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//#3
// 如果图片展示没有延迟展示占位图片,在主线程上调用
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
//#4
// 判断url是否为空,如果非空那么一个全局的SDWebImageManager就会调用以下的方法获取图片:
// loadImageWithURL:options:progress:completed:
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
id operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// 省略不必要的代码,这里主要是展示下载好的图片
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
//#5
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);
}
});
}
}
最开始的两行代码:
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
框架中的所有操作实际上都是通过一个 operationDictionary 来管理, 而这个字典实际上是动态的添加到 UIView 上的一个属性, 至于为什么添加到 UIView 上, 主要是因为这个 operationDictionary 需要在 UIButton 和 UIImageView 上重用, 所以需要添加到它们的根类上.
这两行代码是要保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突, 它的调用使当前UIView中的所有操作都被cancel.不会影响之后进行的下载操作.
接下来就是
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
给当前UIView绑定下载图片链接NSURL地址,这里是方便的获取当前视图显示图片的地址
如果图片展示没有延迟展示占位图片,在主线程上设置占位图片,一般而言由于setImageBlock为空,根据设备以及UIView类型进行图片设置。
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
- (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
}
接下来会检测传入的 url 是否非空, 如果为空那就删除ActivityIndicator,并回调completedBlock;
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);
}
});
非空那么一个全局的 SDWebImageManager 就会调用以下的方法获取图片:
[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:];
在这个方法之后, 即返回operation的同时, 也会向 operationDictionary中添加一个键值对, 来表示操作正在进行。
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
3、老司机SDWebImageManager
The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.It (SDWebImageManager) 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.
用于处理异步下载和图片缓存的类,这个类的主要作用就是在UIView+WebCache和SDWebImageDownloader,SDImageCache之间构建一个桥梁,使它们能够更好的协同工作。
这个框架是如何组织的?
这张图片已经将这个框架是如何组织的基本展示了出来,
UIView+WebCache
直接为表层的
UIKit
框架提供接口, 而
SDWebImageManger
负责处理和协调
SDWebImageDownloader
和
SDWebImageCache
. 并与
UIKit
层进行交互, 而底层的一些类为更高层级的抽象提供支持.
继续来看上面说的那个下载图片的方法
[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]
首先,判断url是否有效, 如果传入参数的是NSString类型就会被转换为NSURL(当传url是NSString的时候,转换而来的NSURL并不一定是真实有效的下载地址)如果转换失败,那么url会被赋值为空。
初始化一个SDWebImageCombinedOperation类型的operation用于返回,这里返回下载的操作出错信息[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]。
{
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
// 确定url是否被正确传入,如果传入参数的是NSString类型就会被转换为NSURL.如果转换失败,那么 url会被赋值为空,这个下载的操作就会出错.
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
// 这里仅仅是将实现了SDWebImageOperation协议的NSObject子类包装成一个看着像NSOperation其实并不是NSOperation的类,而这个类唯一与NSOperation的相同之处就是它们都可以响应cancel方法。
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
// 当前下载的图片地址,是否包含在以前失败NSMutableSet里面
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
// 当传url是NSString的时候,转换而来的NSURL并不一定是真实有效的下载地址
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
/*
实际上是在主线程中调用回调completedBlock
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completedBlock) {
completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil], SDImageCacheTypeNone, YES, url);
}
});
*/
return operation;
}
来到这里说明一切正常
首先
将operation加到runningOperations数组中
再
使用key在缓存中查找以前是否下载过相同的图片。先通过url获取对应的key,一般情况是url.absoluteString,使用key在缓存中查找以前是否下载过相同的图片的NSOperation,并赋值给上面那个不正经的operation中正经的cacheOperation。
如果我们在缓存中查找到了对应的图片NSOperation, 那么我们直接调用completedBlock回调块结束这一次的图片下载操作,且从runningOperations删除operation
如果我们没有找到图片, 那么就会调用 SDWebImageDownloader的实例方法: downloadImageWithURL:options: progress: completed:
如果这个方法返回了正确的downloadedImage, 那么我们就会在全局的缓存中存储这个图片的数据,并调用completedBlock对 UIImageView或者UIButton设置图片, 或者进行其它的操作;
最后
, 我们将这个subOperation的cancel操作添加到operation.cancelBlock中,方便操作的取消。
// 加到正在下载NSMutableArray数组中
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {...}];
@synchronized(operation) {
// Need same lock to ensure cancelBlock called because cancel method can be called in different queue
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}
return operation;
}
4、SDImageCache
typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
1.判断参数key是否合法,如果不合法则回调doneBlock,image=nil,data=nil,cacheType=SDImageCacheTypeNone,并返回nil;
2.是否缓存
①内存有则回调doneBlock,image=内存图片,data=内存图片二进制数据,cacheType=SDImageCacheTypeMemory,并返回nil;没有则在磁盘中查找
②磁盘中有且支持内存缓存将图片缓存中,并回调doneBlock,image=磁盘中图片,data=磁盘中图片二进制数据,cacheType=SDImageCacheTypeDisk
3.内存、磁盘都没有,返回一个空的operation进行网络下载图片
- (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.images) {
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;
}
5、SDWebImageDownloader
这个类的核心功能就是下载图片, 而核心方法就是下面的这个实现
- (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 *{
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
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;
}];
}
上面的方法直接调用了另一个关键的方法,它为这个下载的操作添加回调的块, 在下载进行时, 或者在下载结束时执行一些操作。
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// 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.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
__block SDWebImageDownloadToken *token = nil;
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
});
};
}
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
6、SDWebImageDownloaderOperation
这个类就是处理下载图片的HTTP请求,当这个类的实例被加入队列之后,start 方法就会被调用,而 start 方法首先就会创建一个NSURLSession
- (void)start
{
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
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];
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(), ^{
// 发出一个SDWebImageDownloadStartNotification通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
}
在start方法调用之后, 就是NSURLSessionTaskDelegate、NSURLSessionDataDelegate中代理方法的调用:
1.在URLSession:dataTask:didReceiveData:方法里面不停回调progressBlock来提示下载的进度;
2.下载完成之后在URLSession:task:didCompleteWithError:代理方法中调用completionBlock来完成最后UIImageView.image的更新;
这里调用的progressBlock、completionBlock、cancelBlock都是在之前存储在URLCallbacks字典中的。
以上就是前面提到的加载图片方法执行的全部过程了。
参考
iOS 源代码分析 --- SDWebImage