本文基于SDWebImage 4.0分析
SDWebImage 是一个支持异步下载加二级缓存的UIImageView 的扩展框架,主要功能如下:
- 扩展UIImageView,UIButton,MKAnnotationView,增加网络图片与缓存管理。
- 一个异步的图片加载器
- 一个异步的内存+磁盘图片缓存,拥有自动的缓存过期处理机制。
- 支持后台图片解压缩处理
- 确保同一个URL的图片不被多次下载,确保虚假的URL不会被反复加载
- 确保下载及缓存时,主线程不被阻塞
- 使用GCD,NSOperation&NSOperationQueue与ARC
看下SDWebImage 的 功能框架图:
- UIImageView + WebCache 和其他的扩展都是用户交互直接打交道的。
- SDWebImageManager是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁。
- SDImageCache负责图片的缓存,包括内存缓存和磁盘缓存
- SDWebImageDownloader负责维持图片的下载队列;
- SDWebImageDownloaderOperation负责真正的图片下载请求;
- SDWebImageDecoder负责图片的解压缩;
- SDWebImagePrefetcher负责图片的预取;
看下总的模块图:
下面按照图片等加载流程来分析:
1.以 UIImageView+WebCache 扩展为例,其他扩展一样的:
- (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 //没有设置operationkey
setImageBlock:nil //这里传了空
progress:progressBlock
completed:completedBlock];
}
上面这一大段代码是:UIImageView 的下载一个图片对外的接口,用户可以根据自己的需求设置不同的操作,包括图片url,占位图,下载的options,下载进度回调,下载完成回调。
2.在看UIView 中的接口:
- (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 {
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); //检测操作key为空显示为该类名
[self sd_cancelImageLoadOperationWithKey:validOperationKey];//下载之前先根据操作key取消之前下载队列中的任务/
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//再将对应的imageurl 存起来
// SDWebImageDelayPlaceholder枚举值的含义是取消网络图片加载好前展示占位图片。
// 所以在这里并不能直接把placeholder直接赋值给self.image,而要用if条件排除这种情况。
//就是说不是SDWebImageDelayPlaceholder这种情况的进入设置占位图片
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) { //如果图片url 不为空
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
//开始执行下载操作。然后回调下载的数据, 下面SDWebImageManager这个类很重要
__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) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{ //dispatch_main_sync_safe : 保证block能在主线程进行 ,更新UI
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { //设置不自动设置图片,一般这个条件是满足不了的。。
completedBlock(image, error, cacheType, url);
return;
} else if (image) {//如果图片下载回调成功,self 设置图片,先设置完成下面再回调。。。高明之处。
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {//若果没下载到数据,继续设置占位图片
if ((options & SDWebImageDelayPlaceholder)) {//除了这种条件 其他都不满足。。
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {//若果完成,继续回调到imageview层
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else { //如果url 是空的操作。。。停止菊花
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) { // 向上层回调一个url 错误的异常
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
上面操作涉及的UIView+WebCacheOperation
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIView+WebCacheOperation.h"
#if SD_UIKIT || SD_MAC
#import "objc/runtime.h"
static char loadOperationKey;
typedef NSMutableDictionary SDOperationsDictionary;
@implementation UIView (WebCacheOperation)
//操作字典
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
//把当前操作以及对应的key 存入字典中
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
}
}
}
//取消加载的操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
// 仅仅根据 key 移除对应的当前 UIView 的操作,并没有取消它们。
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
[operationDictionary removeObjectForKey:key];
}
}
@end
#endif
3.按照上面的步骤下一步我们该分析一下SDWebImageManager这个集缓存和下载与一体的管理类了。
- 根据不同的操作下载不同的处理
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
// 默认情况下,当URL下载失败时,URL会被列入黑名单,导致库不会再去重试,该标记用于禁用黑名单
SDWebImageRetryFailed = 1 << 0,
// 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时
SDWebImageLowPriority = 1 << 1,
// 该标记禁用磁盘缓存
SDWebImageCacheMemoryOnly = 1 << 2,
// 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。
// 默认情况下,图像在下载完成后一次性显示
SDWebImageProgressiveDownload = 1 << 3,
// 即使图片缓存了,也期望HTTP响应cache control,并在需要的情况下从远程刷新图片。
// 磁盘缓存将被NSURLCache处理而不是SDWebImage,因为SDWebImage会导致轻微的性能下载。
// 该标记帮助处理在相同请求URL后面改变的图片。如果缓存图片被刷新,则完成block会使用缓存图片调用一次
// 然后再用最终图片调用一次
SDWebImageRefreshCached = 1 << 4,
// 在iOS 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成
// 如果后台任务超时,则操作被取消
SDWebImageContinueInBackground = 1 << 5,
// 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理存储在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
// 允许不受信任的SSL认证
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
// 默认情况下,图片下载按入队的顺序来执行。该标记将其移到队列的前面,
// 以便图片能立即下载而不是等到当前队列被加载
SDWebImageHighPriority = 1 << 8, //256
// 默认情况下,占位图片在加载图片的同时被加载。该标记延迟占位图片的加载直到图片已以被加载完成
SDWebImageDelayPlaceholder = 1 << 9, //512
// 通常我们不调用动画图片的transformDownloadedImage代理方法,因为大多数转换代码可以管理它。
// 使用这个票房则不任何情况下都进行转换。
SDWebImageTransformAnimatedImage = 1 << 10, //1024
// 下载完成后避免自动设置图片到view上,用户自己去操作。
SDWebImageAvoidAutoSetImage = 1 << 11,
//缩小图片比例,默认不缩小的
SDWebImageScaleDownLargeImages = 1 << 12
};
这个类的核心方法:
- (id )loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 使用断言来保证完成的 Block 不能为空,也就是说如果你不需要完成回调,直接使用 SDWebImagePrefetcher 就行
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// 保证 URL 是 NSString 类型,转换成 NSURL 类型
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// 保证 url 为 NSURL 类型
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//这里有出现了个operation操作,自定义的
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
// 判断是否为下载失败的 url
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
// 保证线程安全
isFailedUrl = [self.failedURLs containsObject:url];
}
}
// 如果是失败的 url 且 operations 不为 SDWebImageRetryFailed,或者 url 为空直接返回错误
// 完整的url字符串 取消下载直接return;
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];
}
// 获取 url 对应的 Key
NSString *key = [self cacheKeyForURL:url];
//1. 从 Cache 中获取图片,它结合 option,进行不同的操作
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//如果 Operation 已经取消,则移除,并结束程序的执行
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
//(没有缓存图片) || (即使有缓存图片,也需要更新缓存图片)(可能有缓存存在。。。) || (代理没有响应imageManager:shouldDownloadImageForURL:消息,默认返回yes,需要下载图片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下载图片)
// &&&&&&&&&&&&&&&&&&
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//1. 存在缓存图片 && 即使有缓存图片也要下载更新图片
if (cachedImage && options & SDWebImageRefreshCached) {
//如果缓存中找到了图像,但提供了SDWebImageRefreshCached,请通知有关缓存的图像
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// 继续执行。。。。。
// SDWebImageDownloaderOptions 根据不同的选项做不同的操作,
// 根据 SDWebImageOptions 转换成对应的 SDWebImageDownloaderOptions。
// 这里需要注意位运算,根据位运算可以计算出不同的选项。
// 那么使用位定义的枚举和用普通定义的枚举值有什么优缺点?需要读者考虑。比如下面这两种定义方法个的优缺点。
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (cachedImage && options & SDWebImageRefreshCached) {
//强制渐进关闭,如果图像已经缓存但强制刷新
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
//忽略从NSURLCache读取的图像,如果缓存的图像,但强制刷新
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
/**********************************************************************************************************************/
//**************下面是不存在缓存图片时候开始下载**************//
// 使用 imageDownloader 下载图片,下载完成后保存到缓存,并移除 Operation。
// 如果发生错误,,需要将失败的 Url 保存到 failedURLs,避免实效的 Url 多次下载。
// 这里需要注意一个 delegate ([self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL: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) {
} 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) { //这个是没法下载
// 在错误url名单中添加当前的url
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}else {//这个是另一个情况下载成功了
// 如果需要下载失败后重新下载,则将当前url从失败url名单里移除
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); //是否进行硬盘缓存
//(即使缓存存在,也要刷新图片) && 缓存图片 && 不存在下载后的图片:不做操作
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
//(下载图片成功 && (没有动图||处理动图) && (下载之后,缓存之前 先处理图片) SDWebImageManagerDelegate 这个协议
} 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), ^{
//转换后的图片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
//判断是否转换成功
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//缓存图片
[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];
}
}];
/**********************************************************************************************************************/
// 取消的block
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 {
//没有缓存的图片,而且下载被代理终止了
__strong __typeof(weakOperation) strongOperation = weakOperation;
// 调用完成的block
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
其中有个类:SDWebImageCombinedOperation
@interface SDWebImageCombinedOperation : NSObject
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end
实现了这个抽象接口:
#import
@protocol SDWebImageOperation
- (void)cancel;
@end
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
_cancelBlock = nil;
}
}
缓存操作相关:
//通过key 查询缓存值
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
// key 为空直接回调空
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 第一步检查内存中的缓存
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
//因为图片有缓存可供使用,所以不用实例化NSOperation,直接范围nil
return nil;
}
//================查看磁盘的缓存=================//
NSOperation *operation = [NSOperation new];
//异步操作
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// 在用之前就判断operation是否被取消了,作者考虑的非常严谨
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
// cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
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;
}
下载操作相关:
//该方法主要操作在于创建一个SDWebImageDownloaderOperation 对象,然后对该进行属性设置,再将该对象加入名字为downloadQueue 的队列。
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
// block 返回值是 SDWebImageDownloaderOperation,在 block 中创建一个 SDWebImageDownloaderOperation
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;
}
//为了防止潜在的重复缓存(NSURLCache + SDImageCache),我们禁用缓存的图像请求,如果另有说明
// @method initWithURL:
// @abstract使用给定的URL初始化一个NSURLRequest
// 缓存策略。
// @discussion这是指定的初始化程序
// NSURLRequest类。
// @param URL请求的URL。
// @param cachePolicy请求的缓存策略。
// @param timeoutInterval请求的超时间隔。见
// 有关更多信息的 timeoutInterval tt>的评论
// 超时间隔。
// @result初始化的NSURLRequest。
//以这种方式生成一个request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) 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;
}
/**********************上面是配置一个request****************************/
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
// 处理 HTTP 认证的,大多情况不用处理
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
// 设置 Operation 的优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//加入队列中开始下载了。。。
[sself.downloadQueue addOperation:operation];
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
//通过系统地添加新的操作作为最后一个操作的依赖关系来模拟LIFO执行顺序
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation; //返回给自己用........
}];
}
初始化相关操作:
//有三个方法用来创建NSURLSessionConfiguration:
//
//defaultSessionConfiguration- 使用全局的cache,cookie和credential storage objects来创建configuration对象。
//
//ephemeralSessionConfiguration – 这个configuration用于“private” sessions,还有对于cache, cookie, or credential storage objects的非永久存储。
//
//backgroundSessionConfiguration – 做远程push通知或是应用程序挂起的时候就要用到这个configuration。
//初始化一些配置,包括操作类,最大队列数,超时时间,还有一个NSURLSession 并设置了 代理
//在SDWebImageDownloader的初始化方法中有创建了一个NSURLSession,这个对象是用来执行图片下载的网络请求的,它的代理对象是SDWebImageDownloader。所以我们在SDWebImageDownloader类里面找到对应的NSURLSession的代理方法,那么就可以看到下载操作的一些细节。
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
/**
*为此任务创建会话
*作为代理队列发送nil,以便会话创建一个用于执行所有委托的串行操作队列
*方法调用和完成处理程序调用。
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
return self;
}
下面是当前类的session 的代理方法:
#pragma mark NSURLSessionDataDelegate
// 1.接收到服务器的响应
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSLog(@"xxxxxxxxxxxxxxx11111111111");
// 取得 SDWebImageDownloaderOperation 对象将URLSession的回调转发给它
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
NSLog(@"uuuuuuuuuuuuuuuuuu11111111111");
// 取得 SDWebImageDownloaderOperation 对象将URLSession的回调转发给它
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
...... 省略部分
......
dataOperation 继承抽象接口的方法。
核心功能SDWebImageDownloaderOperation:
继承自NSOperation:
- (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];
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];
self.executing = YES;
}
[self.dataTask resume];
if (self.dataTask) {
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
} 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
}
下面是收到数据的回调:
#pragma mark NSURLSessionDataDelegate
//收到响应开始,这个是sdwebimageDownloader 中的代理方法直接调用过来的
//收到网络请求的响应之后判断statusCode,若是正常的statusCode,
//那么就根据数据量创建对应大小的NSMutableData,发送对应的通知。若是出现异常,那么就做好对应的错误回调和发送对应的错误通知。
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSLog(@"yyyyyyyyyyyyyyyy2222222222222");
//'304 Not Modified' is an exceptional one
// (没有statusCode) 或者 (statusCode小于400 并且 statusCode 不等于304)
// 若是请求响应成功,statusCode是200,那么会进入这个代码分支
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
//期望收到的数据量
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
//下载过程回调,收到数据量为0
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
//根据期望收到的数据量创建NSMutableData,该NSMutableData用户保存收到的数据量
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
//保存响应对象
self.response = response;
//发送网络请求收到响应的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
}
else {
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
if (code == 304) {
//code 304 取消下载 直接返回缓存中的内容
[self cancelInternal];
} else {
//数据请求任务取消
[self.dataTask cancel];
}
//发送停止下载的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
// 错误处理回调
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
}
// 调用completionHandler回调
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
下载完成的回调:
#pragma mark NSURLSessionTaskDelegate
//数据请求任务完成的代理方法
//图片数据下载完成之后,最重要的一件事就是将图片数据转成UIImage对象。
//然后通过 - (void)callCompletionBlocksWithImage:imageData:error:finished: 方法
//将该UIImage对象和图片数据的data 传给SDWebImageDownloader。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"wwwwwwwwwww2222222222222");
@synchronized(self) {
self.dataTask = nil;
dispatch_async(dispatch_get_main_queue(), ^{
// 发送停止下载通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) {
//发送完成下载通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
}
if (error) {
//错误回调
[self callCompletionBlocksWithError:error];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
* Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
* and images for which responseFromCached is YES (only the ones that cannot be cached).
* Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
// hack
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
} else if (self.imageData) {
//处理图片方向和图片格式
UIImage *image = [UIImage sd_imageWithData:self.imageData];
// 获取缓存key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
// 处理图片的缩放倍数
image = [self scaledImageForKey:key image:image];
// Do not force decoding animated GIFs
// 动图不要解压
if (!image.images) {
if (self.shouldDecompressImages) {
//如果要大图
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
#if SD_UIKIT || SD_WATCH
image = [UIImage decodedAndScaledDownImageWithImage:image];
[self.imageData setData:UIImagePNGRepresentation(image)];
#endif
} else {
image = [UIImage decodedImageWithImage:image];
}
}
}//下面是错误回调
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {//正确回调图片和数据,到此结束了。。。。操。。
[self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
[self done];
}
至此一个图片下载完成
最近心情不好,抽空再把细节介绍清楚。
友情链接:
http://www.jianshu.com/p/13e0755c1184