闲来无事解读一下 SDWebImage 源码,一方面检验一下自己综合知识的掌握,一方面学习一下 SDWebImage 的编程思想。
一 UIButton/UIImageView 设置图片
通过 UIImageView+WebCache、UIButton+WebCache 类别为UIButton/UIImageView 增加便利设置方法,类别中提供全能初始化方法,最后调用 UIView+WebCache 类别中方法。其中 UIButton类别中通过绑定增加 imageURLStorage 字典,按状态存储不同状态的图片URL;
UIView+WebCache 类别中的最后的图片设置方法:
- (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 {
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> 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 {
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);
}
});
}
}
UIButton/UIImageView中的图片设置方法最后都会调用此方法,
- 方法中先判断 operationKey 是否存在,如果不存在根据类名生成。
- sd_cancelImageLoadOperationWithKey 根据 operationKey 去取消之前相应的 operation 类别中通过绑定为UIView 增加 SDOperationsDictionary *operations 字典用来存储图片设置任务, 其中字典的key为NSString *类型,UIButton 所使用的key为UIButton的各个状态,其他类型使用的key为类名。 因为UIButton中有多个状态且区分为image、backgroundImage,所以有多张图片,对应多个key值。UIImageView只有image,所以只有一个key。
- 判断是否 SDWebImageDelayPlaceholder 属性来进行 placeholder 的设置
- 判断 url 是否存在 不存在 直接调用 completedBlock 返回空数据 和 "Trying to load a nil url" 错误
- 如果 url 存在 调用 SDWebImageManager.sharedManager 的
loadImageWithURL:options:progress:completed:
方法 生成(id <SDWebImageOperation>)
类型数据 并将数据使用之前生成的 operationKey 作为key存储 operations 字典中。
- 接下来看 SDWebImageManager.sharedManager 中的实现
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 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.
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;
}
__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];
}
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//第5条详解
}];
return operation;
}
- 断言判断 completedBlock 如果没有则报错。
- 转换url 如果url 不存在则设置 url = nil
- 创建 SDWebImageCombinedOperation * 对象 要返回的目标 这里先用__block声明是option可在block中进行修改 然后赋值给 __weak 修饰的对象 防止循环引用
- 判断
failedURLs (NSMutableSet<NSURL *> *)
中是否包含url。此集合包含请求失败的url 使用@synchronized (self.failedURLs)
方法保证failedURLs 的操作线程安全。 - 判断url 是否可用(SDWebImageRetryFailed 选项代表之前的failedURLs中的url是否可继续请求,不包含此选项则直接返回不在继续) 如果不可用直接返回 error.code = NSURLErrorFileDoesNotExist
- 利用
@synchronized (self.runningOperations) { [self.runningOperations addObject:operation]; }
将之前生成的operation添加到 正在运行集合中。 - 根据 url 生成唯一标识key
cacheKeyForURL
默认为url.absoluteString, 可通过 设置 SDWebImageManager 的 cacheKeyFilter来改变规则 - 利用 SDImageCache 的
queryCacheOperationForKey:done:
方法为之前生成的operation的 cacheOperation 赋值 缓存操作
- 接下来看 SDImageCache 中的实现
- (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;
}
- 此方法实现了根据key查找图片的过程
- 先判断是否存在key 不存在 调用 doneBlock 传入nil
- 查找内存缓存
imageFromMemoryCacheForKey
先查找内存中是否存在key对应的图片,如果存在 则判断是否是动画图片数组,存在则在路径中查找图片数组NSData。 - 查找磁盘缓存 生成 NSOperation 并在串行 ioQueue 执行查找操作 先查找NSData 将NSData根据 SDImageCacheConfig 转换成 UIImage。 并更新 memCache 中的存储数据 调用doneBlock 并返回 NSOperation对象
- 下面在返回查看 doneBlock 中传入的内容
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];
}
// download if no image or requested to refresh anyway, and download allowed by delegate
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) {
// 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;
}
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 {
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), ^{
//第七条详解
}];
@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];
};
}
} 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
__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];
}
- 先判断 SDWebImageCombinedOperation 对象当前是否已经取消 未取消则继续 已取消 则 删除 runningOperations 中存储的对象 利用
@synchronized (self.runningOperations)
线程安全 - 判断缓存图片是否存在或者要刷新缓存 如果存在 回调 completedBlock 并安全删除 runningOperations 中存储的对象
- 如果缓存不存在且 下载代理不允许下载此 url 图片 则 用nil 调用 completedBlock 并安全删除 runningOperations 中存储的对象
- 如果缓存不存在 且 下载代理允许下载此 url 则进行后续下载操作
- 判断是否要刷新缓存 如果要刷新缓存 且 缓存图片已存在 则调用 completedBlock 并继续后续操作
- 根据外部options 配置下载 downloaderOptions
- 根据 SDWebImageDownloader 创建下载任务 并为 operation.cancelBlock 赋值 operation取消时 删除下载任务 并删除 operation
- 下面看 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;
}
先看第二个方法
- 先判断 url 是否存在 存在则继续
- 在并发 barrierQueue 队列中
dispatch_barrier_sync
同步添加一个下载任务 - 下载任务由 SDWebImageDownloaderOperation 完成 SDWebImageDownloaderOperation 是NSOperation的自定义子类 并声明 URLOperations 字典 来存储所有operation
- 根据URL 在 URLOperations 查找相应的 operation 如果没有找到则调用传入的 createCallback 新建operation 设置 completionBlock 下载完成后删除URLOperations中相应的键值对
- 为operation 添加相应的 回调的block 用字典存储, 内部使用 callbackBlocks 数组包含所有相关回调字典 有点像 AFNetWorking的请求代理数组
- 将回调block的字典存储到 SDWebImageDownloadToken 对象中,一个 SDWebImageDownloadToken 对象对应一个回调 并包含URL。 可能多个SDWebImageDownloadToken 对象对应同一个URL 但是回调不一样。对应同个URL的多个请求
再回过去看第一个方法
- 第一个方法中 直接调用第二个方法 然后返回 会传入一个 创建下载operation的block
- 根据配置创建 SDWebImageDownloaderOperation 对象 并创建 NSMutableURLRequest 网络请求对象
- 将 SDWebImageDownloaderOperation 对象 添加到 NSOperationQueue 中 最大并发量默认为 6
- 然后根据 SDWebImageDownloader executionOrder属性判断下载顺序来调整 NSOperationQueue中 NSOperation 的依赖关系 默认为 FIFO 如果是 LIFO则用最后一个任务依赖新添加的任务来实现
- 返回去看 下载完成 Block 的实现
__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 {
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), ^{
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];
}
- 此方法处理 网络请求 数据返回 回调
- 判断回调结果 如果错误则 调用回调传入错误结果 并将URL 放入 failedURLs中
- 如果成功 则将URL从 failedURLs 移除 并判断进行存储的方式 然后回调出来结果 并最后将 SDWebImageCombinedOperation 对象移除
- 再返回去看 最开始 UIView+WebCache 中设置图片所传入completed 回调
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) { return; }
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
BOOL shouldUseGlobalQueue = NO;
if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
}
dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();
dispatch_queue_async_safe(targetQueue, ^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
});
此回调主要处理获取完图片之后的图片赋值操作
- SDWebImageAvoidAutoSetImage 选项代表获取图片后不自动设置图片 只返回结果
- 继续往下 获取图片 并判断附加参数 是否要在全局队列中设置图片 默认在主队列设置 在目标队列设置图片 并调用回调方法
- 设置图片流程全部完成
总结
- UIView类别调用设置图片 设置完成后的图片设置Block
- 调用 SDWebImageManager
loadImageWithURL:options:progress:completed:
方法判断URL合法性和获取URL对应的key 调用 SDImageCache 查找缓存 并设置缓存查询处理Block - SDImageCache 查找缓存 先查找内存 在查找磁盘 并调用 第二步传进来的 Block
- 缓存查询处理Block被调用 进行判断 处理缓存结果 是否直接返回或进行网络下载
- 如果要下载 则调用 SDWebImageDownloader
downloadImageWithURL:options:progress:completed:
方法进行下载 并传入下载完成的回调出来 下载图片结果 - SDWebImageDownloader 中 创建 SDWebImageDownloaderOperation 对象 并设置 NSMutableURLRequest 网络请求配置 生成 operation对象 放入一个并发数为6 的 NSOperationQueue 中执行 并设置operation 回调 将对象封装返回
- 回调第五步传进来的Block 进行图片存储 并调用 第一步传进来的Block
- 第一步的Block会根据options判断 是否对视图image赋值 并回调处理结果
整个流程涉及到多线程以及同步的问题 其中
- SDImageCache 中包含一个串行ioQueue队列来进行磁盘数据的存储和查询
- SDWebImageDownloader 中使用一个并发barrierQueue队列并用dispatch_barrier_sync方法来管理 SDWebImageDownloaderOperation 的创建以及取消等处理(不太明白为什么不用一个串行队列)
- SDWebImageDownloader中使用一个最大并发量为6的NSOperationQueue来处理SDWebImageDownloaderOperation的执行
- SDWebImageDownloaderOperation 中使用了并发 barrierQueue 队列 来管理回调数组 还有其余数组、集合类型都使用@synchronized()来保证线程安全
大致看了一下整体的图片设置流程 下一步详细看一下 SDWebImageDownloaderOperation、SDImageCache、SDWebImageDownloader以及图片解压缩和其他类别的功能