前言
SDWebImage是ios中一款优秀的图片缓存框架,已经经历了若干版本,虽说性能不是最高的,但从安全和维护角度,非常适合大多数人使用 — SDWebImage源码
主框架介绍
其主要逻辑,如下所示
webCache: 对UIView系列的分类信息,里面包含了取消下载调用操作,调用ImageManager获取图片内容信息,以及获取到图片后的处理操作
SDWebImageManager: 用于获取图片内容的一个类,作为中间管理类的身份,来协调缓存获取和网络下载之间的操作
SDImageCache: 内存缓存和磁盘缓存的操作类,通过此类优先从内存中获取图片,其次从磁盘缓存获取内容
SDMemoryCache: 负责维护高速缓存的读取等操作
SDDiskCache: 负责维护磁盘缓存的读取等操作
SDWebImageDownLoader: 负责管理图片的下载操作,包括到SDWebImageDownloaderOperation网络请求的映射操作
SDWebImageDownloaderOperation: 自定义NSOperation的子类,用于更加精准的控制图片下载的过程,且为下载过程中网络数据的实际接收类,避免类多个数据的写入保存问题,接收数据完毕后回调内容
SDWebImage源码介绍
SDWebImage源码主要介绍上面几个类,用于了解其核心逻辑,以平时调用的分类方法为基准,依次介绍源码
且会介绍一些里面的小tips,以便于我们理解和运用
WebCache分类
通过分类的的图片加载方法调用,最终会走到下面的方法中去
- (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 {
if (context) {
// copy to avoid mutable object
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//没有key的情况保存key
if (!validOperationKey) {
validOperationKey = NSStringFromClass([self class]);
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
context = [mutableContext copy];
}
self.sd_latestOperationKey = validOperationKey;
//取消本次key对应的operation子队列请求,避免性能浪费和错误回调,key和当前的View是绑定的
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
if (!(options & SDWebImageDelayPlaceholder)) {
//没有设置延迟加载placeholder图片,则立即加载placeholder,在进行下载操作
//这个是同步队列,通过queue的标签判断是否是主队列,而不是通过线程判断
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil
basedOnClassOrViaCustomSetImageBlock:setImageBlock
cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
//这里主要是设置Progress回调等相关
...
//使用SDWebImageManager加载图片
@weakify(self);
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) {
@strongify(self);
if (!self) { return; }
//标记progress完成
// if the progress not been updated, mark it to complete state
if (imageProgress && finished && !error &&
imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// check and stop image indicator
if (finished) {
[self sd_stopImageIndicator];
}
#endif
//根据options检查是否需要回调和主动设置image
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
//设置回调
SDWebImageNoParamsBlock callCompletedBlockClosure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
//直接更新view内容,在需要的时候
[self sd_setNeedsLayout];
}
//回调image和data
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, data, error, cacheType, finished, url);
}
};
if (shouldNotSetImage) {
//不需要自动设置图片,则在主队列回调
dispatch_main_async_safe(callCompletedBlockClosure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
...
//根据图片的类型,在主线程中设置图片,并回调block
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[self sd_setImage:targetImage imageData:targetData
basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition
cacheType:cacheType imageURL:imageURL];
#else
[self sd_setImage:targetImage imageData:targetData
basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType
imageURL:imageURL];
#endif
callCompletedBlockClosure();
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
#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);
}
});
}
}
复制代码
dispatch_main_async_safe
为定义的一个判断主线程,并且在主线程回调block的宏,可以看到,是根据队列的标签label来判断是否是主队列,而不是使用当前线程是否是主线程判断,由于主队列为串行队列,这样判断也方便使用宏直接调用block
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
复制代码
SDWebImageManager图片加载管理类
其为一个图片加载管理类,通过调用SDWebImageCache和SDWebImageDownloader来实现优先缓存加载,其次网络下载
下面的方法主要是判断是否需要架子啊的逻辑,最后会进入callCacheProcessForOperation方法中去
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {、
...
//url有问题直接失败
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
[self callCompletionBlockForOperation:operation
completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain
code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
return operation;
}
SD_LOCK(_runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(_runningOperationsLock);
// Preprocess the options and context arg to decide the final the result for manager
SDWebImageOptionsResult *result = [self processedResultForURL:url
options:options context:context];
// Start the entry to load image from cache
//开始获取图片
[self callCacheProcessForOperation:operation url:url
options:result.options context:result.context
progress:progressBlock completed:completedBlock];
return operation;
}
复制代码
加载缓存
实际的加载图片,缓存与网络请求调用逻辑在下面的方法中
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use
//获取SDImageCache缓存管理类
id<SDImageCache> imageCache;
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// Get the query cache type
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
// Check whether we should query cache
//判断是否需要从缓存中获取
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
if (shouldQueryCache) {
//根据url获取key
NSString *key = [self cacheKeyForURL:url context:context];
@weakify(operation);
//通过key到SDImageCache类中获取缓存内容,后面介绍
operation.cacheOperation = [imageCache queryImageForKey:key
options:options context:context cacheType:queryCacheType
completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable
cachedData, SDImageCacheType cacheType) {
@strongify(operation);
//operation不存在或者任务被取消,就什么都不做了,直接回调错误
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
[self callCompletionBlockForOperation:operation completion:completedBlock
error:[NSError errorWithDomain:SDWebImageErrorDomain
code:SDWebImageErrorCancelled
userInfo:@{NSLocalizedDescriptionKey :
@"Operation cancelled by user during querying the cache"}]
url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
} else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
//查询原始图,代替下载的,如果原始图查询失败,则才开始下载,由于逻辑与方法基本一致,就介绍了
// Have a chance to query original cache instead of downloading
[self callOriginalCacheProcessForOperation:operation
url:url options:options context:context progress:progressBlock
completed:completedBlock];
return;
}
//直接网络请求下载图片
// Continue download process
[self callDownloadProcessForOperation:operation url:url
options:options context:context cachedImage:cachedImage
cachedData:cachedData cacheType:cacheType progress:progressBlock
completed:completedBlock];
}];
} else {
//直接网络请求
// Continue download process
[self callDownloadProcessForOperation:operation url:url
options:options context:context cachedImage:nil cachedData:nil
cacheType:SDImageCacheTypeNone progress:progressBlock
completed:completedBlock];
}
}
复制代码
从网络下载图片
从网络下载图片的调用方法如下所示
// Download process
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image loader to use
//获取实现ImageLoader协议的代理类,来调用下载方法
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// Check whether we should download image from network
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)]
|| [self.delegate imageManager:self shouldDownloadImageForURL:url]);
if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
} else {
shouldDownload &= [imageLoader canRequestImageForURL:url];
}
//需要下载则开始下载图片
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
[self callCompletionBlockForOperation:operation completion:completedBlock
image:cachedImage data:cachedData error:nil cacheType:cacheType
finished:YES url:url];
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
@weakify(operation);
//下载图片方法调用
operation.loaderOperation = [imageLoader requestImageWithURL:url
options:options context:context progress:progressBlock completed:^(UIImage
*downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
//取消下载失败直接回调
if (!operation || operation.isCancelled) {
...
} else {
//到这里就是下载成功了
//如果下载成功则从失败名单删除
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self->_failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self->_failedURLsLock);
}
// Continue store cache process
//对缓存进行处理,包括对旋转图片恢复,磁盘缓存等
[self callStoreCacheProcessForOperation:operation url:url
options:options context:context downloadedImage:downloadedImage
downloadedData:downloadedData finished:finished
progress:progressBlock completed:completedBlock];
}
if (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else if (cachedImage) {
//找到缓存图片,结束
[self callCompletionBlockForOperation:operation 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
[self callCompletionBlockForOperation:operation completion:completedBlock
image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES
url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}
复制代码
SDImageCache快速缓存管理类
其第一个方法主要是判断options的不介绍,主要是下面的方法
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context
cacheType:(SDImageCacheType)queryCacheType
done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
//key不存在直接返回空,相当于找不到
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// Invalid cache type 没有缓存直接结束
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
//如果不是只存放磁盘,则开始从高速缓存内存中获取
UIImage *image;
if (queryCacheType != SDImageCacheTypeDisk) {
image = [self imageFromMemoryCacheForKey:key];
}
//获取到图片
if (image) {
//对图片进行处理,包括旋转图片(图片获取到可能是旋转的)
if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image
Class animatedImageClass = image.class;
if (image.sd_isAnimated ||
([animatedImageClass isSubclassOfClass:[UIImage class]] &&
[animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale
orientation:kCGImagePropertyOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale
orientation:image.imageOrientation];
#endif
}
} else if (options & SDImageCacheMatchAnimatedImageClass) {
// Check image class matching
Class animatedImageClass = image.class;
Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
image = nil;
}
}
}
//如果仅仅从高速缓存中获取,则直接结束
BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) ||
(image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
//高速缓存没有找到,则开始在磁盘缓存
// Second check the disk cache...
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
//定义block,用于io调用,访问磁盘和内存写入
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}
@autoreleasepool {
//从磁盘查询图片数据
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
if (image) {
// the image is from in-memory cache, but need image data
//如果从内存中找到赋值,后续直接返回
diskImage = image;
} else if (diskData) {
//磁盘找到的数据,保存到高速缓存中去
BOOL shouldCacheToMomery = YES;
if (context[SDWebImageContextStoreCacheType]) {
SDImageCacheType cacheType =
[context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll ||
cacheType == SDImageCacheTypeMemory);
}
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData
options:options context:context];
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (shouldQueryDiskSync) {
//根据需要在io队列中回调
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
} else {
//在主线程中回调内容
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
}
};
// Query in ioQueue to keep IO-safe
//在io queue中执行block
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
复制代码
SDWebImageDownloader、SDWebImageDownloaderOperation
SDWebImageDownloader与SDWebImageDownloaderOperation共同完成下载功能
SDWebImageDownloader负责队列的设置和网络请求的代理回调操作
SDWebImageDownloaderOperation继承NSOperation,且代理了SDWebImageDownloader的网络请求操作,负责请求的开始下载取消等操作
SDWebImageDownloader
创建新任务,加入到下载队列中
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
...
//创建新任务,加入到下载队列中
if (!operation || operation.isFinished || operation.isCancelled) {
//设置request等相关参数
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if (!operation) {
SD_UNLOCK(_operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
@weakify(self);
设置自队列的完成后的回调,用于移除信息
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self->_operationsLock);
};
self.URLOperations[url] = operation;
[self.downloadQueue addOperation:operation];
} else {
/
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
if (!operation.isExecuting) {
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
}
SD_UNLOCK(_operationsLock);
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
复制代码
NSURLSession代理操作
下面可以看到,请求根据保存的队列,将子任务取出,将回调方法对应到对应的子任务中代理处理,避免了复杂的数据保存,任务分配等问题
这里只简单介绍SDWebImageDownloader的delegate就不介绍SDWebImageDownloaderOperation中的代理回调了
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(proposedResponse);
}
}
}
#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
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(request);
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
[dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
}
}
复制代码
SDWebImageDownloaderOperation重写
重写了start方法,以便于更好的使用NSOperation
- (void)start {
@synchronized (self) {
//如果任务已经取消,则结束
if (self.isCancelled) {
if (!self.isFinished) self.finished = YES;
// Operation cancelled by user before sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
[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:^{
[wself cancel];
}];
}
#endif
//开始已下载操作
NSURLSession *session = self.unownedSession;
if (!session) {
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.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
self.response = cachedResponse.response;
}
}
if (!session.delegate) {
// Session been invalid and has no delegate at all
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Session delegate is nil and invalid"}]];
[self reset];
return;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
//根据枚举设置任务优先级等
if (self.dataTask) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
self.coderQueue.qualityOfService = NSQualityOfServiceBackground;
} else {
self.dataTask.priority = NSURLSessionTaskPriorityDefault;
self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
}
//开始任务
[self.dataTask resume];
//回调progress
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
});
} else {
//任务出错,结束
if (!self.isFinished) self.finished = YES;
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self reset];
}
}
复制代码
重写了NSOperation的cancel方法
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter]
postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
});
//取消task任务操作
if (self.dataTask) {
[self.dataTask cancel];
self.dataTask = nil;
}
//调整执行状态和完成状态
if (self.isExecuting || self.isFinished) {
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
// Operation cancelled by user during sending the request
//结束回调
[self callCompletionBlocksWithError:
[NSError errorWithDomain:SDWebImageErrorDomain
code:SDWebImageErrorCancelled
userInfo:@{NSLocalizedDescriptionKey :
@"Operation cancelled by user during sending the request"}]];
[self reset];
}
复制代码
可以看到,为了保证属性能够触发KVC,加入了手动KVC操作
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
复制代码
最后
SDWebImage中的下载流程我相信通过简介图就能看到,作者将一个功能拆分出各个模块,通过管理类进行协调调用,例如:SDWebImageManager负责管理缓存类和网络请求类的调用,SDImageCache负责快速缓存和磁盘缓存的处理等
下载模块,通过重写NSOperation,并且将下载任务映射到子任务中去,避免了在一个类中管理多个下载任务的数据以及其他回调等