最近看了SdwebImage的源码,对大神的设计思想和架构模式有了初步的理解和感悟,跟大家分享一下自己的心得,如果有写的不对的地方,欢迎大家批评指正,共同进步。
一.此方法为UIimageView+webCache类的核心方法,UIIamgeView所有的设置图片的接口最终都会来到这里,此方法主要做了下面几件事:
- (void)sd_setImageWithURL:(NSURL*)url placeholderImage:(UIImage*)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock
1.[self sd_cancelCurrentImageLoad];清理正在进行的下载操作,确保一个UIImageView对象同一时间只存在一个下载图片的操作。
2 objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);.动态的给UIImageView对象添加个成员变量缓存下载的URL。
3. if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
如果 options 不是 SDWebImageDelayPlaceholder 将占位图片显示出来
self.image = placeholder;
});
}
默认情况下,当图片在加载中默认图片被加载。如果设置了 options = SDWebImageDelayPlaceholder 将延迟默认图片的显示,直到图片完成加载。
4.id operation = [SDWebImageManager.sharedManagerdownloadImageWithURL:urloptions:optionsprogress:progressBlockcompleted:^(UIImageimage,NSErrorerror,SDImageCacheTypecacheType,BOOLfinished,NSURL*imageURL)
创建下载管理器 SDWebImageManager调用起下载方法 并返回 SDWebImageCombinedOperation的对象,改对象是SDWebImage自定义的对象内部维护了一个NSOperation对象;
- [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
此方法为UIView+WebCacheOperation的方法,内部使用Runtime创建一个字典,改方法的调用是sd_setImageLoadOperation:forKey:添加到字典中,从而缓存operation;
二.接下来来到SDWebImageManager类调用如下方法,此方法主要对于下载并没有做太多的处理,主要是查找缓存,并进一步将下载的操作分配到SDWebImageDownloader中
- (id)downloadImageWithURL:(NSURL*)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
- BOOLisFailedUrl =NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLscontainsObject:url];
}
这里是为了线程安全。
如果URL不存在,或者(options!=SDWebImageRetryFailed 并且 这个URL无法下载图片)这里的SDWebImageRetryFailed默认情况下,当一个url下载失败,如果URL在黑名单中那么SDWebImage库不进行重试。这个标志会使黑名单失效。
if(url.absoluteString.length==0|| (!(options &SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{//这是一个宏定义 内部逻辑为 如果当前线程是主现场那么就在当前线程做操作,如果不是主线程,就返回主线程。
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone,YES, url);//失败回调。
});
return operation;
}
@synchronized (self.runningOperations) {
[self.runningOperationsaddObject:operation];
}
将任务添加到数组中。manger会将所有的正在执行的Operation都放入这个数组,任务完成、失败和关闭都会从数组中移除,改数组后面的操作会用到,假如cancelAll被调用,就会让数组中的每一个Operation都调用cancel方法。
NSString*key = [selfcacheKeyForURL:url];通过URL获取对用的Key。后面通过这个key去缓存中查缓存和本地中对用的图片。
operation.cacheOperation= [self.imageCachequeryDiskCacheForKey:keydone:^(UIImage*image,SDImageCacheTypecacheType) 改方法为查找缓存的方法,内容比较多,解析了我会分开逐句解读。
if(operation.isCancelled) {
@synchronized(self.runningOperations) {
[self.runningOperationsremoveObject:operation];
}
return;
}此方法是将被取消的任务,从数组中移除。
如果((缓存中不存在图片或者options = SDWebImageRefreshCached(更新缓存))&& (代理中未实现图片是否下载的方法||代理中图片是否应该下载方法返回值为YES)
if((!image || options &SDWebImageRefreshCached) && (![self.delegaterespondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegateimageManager:selfshouldDownloadImageForURL:url]))
如果从缓存中获取了图片并且设置了SDWebImageRefreshCached来忽略缓存,则先把缓存的图片返回,然后继续后面的下载
if(image && options &SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
completedBlock(image,nil, cacheType,YES, url);
});
}
4.改方法的调用会来到SDWebImageDownloader的下载方法,此方法是下载操作的核心代码方法的实现稍后会解析
id subOperation = [self.imageDownloaderdownloadImageWithURL:urloptions:downloaderOptionsprogress:progressBlockcompleted:^(UIImage*downloadedImage,NSData*data,NSError*error,BOOLfinished){
__strong __typeof(weakOperation) strongOperation = weakOperation;
//如果图片下载结束以后,对应的图片加载操作已经取消。则什么处理都不做
if(!strongOperation || strongOperation.isCancelled) {
}
if( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else{//网络图片下载成功
//如果有重试失败下载的选项,则把url从failedURLS中移除
if((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否需要保存到磁盘
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//如果设置了强制刷新缓存 && 缓存图片存在 && 下载图片不存在,那么直接返回,不执行回调Block
if(options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block }
//如果成功下载图片 && 图片是动态图片 && 实现了imageManager:transformDownloadedImage:withURL:代理方法,则进行图片的处理
elseif(downloadedImage && (!downloadedImage.images || (options &
SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//获取transform以后的图片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
//存储transform以后的的图片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];
}
}];
三.改方法SDWebImageDownloader的核心方法,所有的图片下载都会分发到该类执行下载,该类为全局唯一的对象内部维护了一个下载的 downloadQueue,下载中的所有的operation所有的任务都会添加到这个队列中,URLCallbacks是一个字典,字典的key为下载的Url,vale为一个数组,数组中放的是个一个字典,字典中有两个键值对,对用的值为两个Block,完成的回调,和进度的回调
- (id)downloadImageWithURL:(NSURL*)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock
1.将完成的回调和下载进度的block添加到URLCallbacks中缓存,如果是第一次下载并调用createCallback()
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL*)url createCallback:(SDWebImageNoParamsBlock)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;
}
dispatch_barrier_sync(self.barrierQueue, ^{
BOOLfirst =NO;
if(!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArraynew];
first =YES;
}
// Handle single download of simultaneous download request for the same URL
NSMutableArray*callbacksForURL =self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if(progressBlock) callbacks[kProgressCallbackKey] = [progressBlockcopy];
if(completedBlock) callbacks[kCompletedCallbackKey] = [completedBlockcopy];
[callbacksForURLaddObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if(first) {
createCallback();//,完成NSMutableURLRequest请求的创建、operation的创建、证书的认证、队列优先级的设置以及任务的执行顺序是按照队列的形式还是栈的形式
}
});
}
2.创建请求 SDWebImageDownloaderUseNSURLCache
//通常情况下request阻止使用NSURLCache. 这个选项会用默认策略使用NSURLCache
NSURLRequestUseProtocolCachePolicy
//默认缓存策略。具体工作:如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取。如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定,如果它指定内容必须重新生效的话。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取
NSURLRequestReloadIgnoringLocalCacheData
//URL应该加载源端数据,不使用本地缓存数据
HTTPShouldHandleCookies :具体可以参考iOS HTTPCookie基本使用 - 简书
HTTPShouldUsePipelining:通常默认情况下请求和响应是顺序的, 也就是说请求–>得到响应后,再请求. 如果将HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求. 这个会很大的提高网络请求的效率,但是也可能会出问题.因为客户端无法正确的匹配请求与响应, 所以这依赖于服务器必须保证,响应的顺序与客户端请求的顺序一致.如果服务器不能保证这一点, 那可能导致响应和请求混乱.
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if(wself.headersFilter) {
request.allHTTPHeaderFields= wself.headersFilter(url, [wself.HTTPHeaderscopy]);
}
else{
request.allHTTPHeaderFields= wself.HTTPHeaders;
}
3.创建请求的Operation 调用改方法会来到SDWebImageDownloaderOperation,initWithRequest:方法并返回operation对象,该类继承NSOperation,并实现了NSURLSessionTaskDelegate, NSURLSessionDataDelegate,并且重写Start方法, [wself.downloadQueueaddOperation:operation];将任务添加到全局的downloadQueue,等于调用了Start方法。在start方法里面根据传入的self.session调用self.dataTask= [sessiondataTaskWithRequest:self.request];接着调用[self.dataTask resume];正式开启下载任务。由于SDWebImageDownloader遵循了<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>并实现了代理方法,self.session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];所以成功之后来到代理方法内,这里作者在SDWebImageDownloader的代理手动调用SDWebImageDownloaderOperation代理方法将请求的数据分发到SDWebImageDownloaderOperation的代理中然后调用成功的回调重新成功的或者失败的数据返回过来,下面的block内部的就是对返回数据的处理,并包括完成后的任务的清除。整个加载过程到这里就结束,关于图片解压缩的流程,后面我会单独写一篇文细说。
operation = [[wself.operationClassalloc]initWithRequest:request
inSession:self.session
options:options
progress:^(NSIntegerreceivedSize,NSIntegerexpectedSize) {
SDWebImageDownloader*sself = wself;
if(!sself)return;
__blockNSArray*callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url]copy];
});
for(NSDictionary*callbacksincallbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlockcallback = callbacks[kProgressCallbackKey];
if(callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage*image,NSData*data,NSError*error,BOOLfinished) {
SDWebImageDownloader*sself = wself;
if(!sself)return;
__blockNSArray*callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url]copy];
if(finished) {
[sself.URLCallbacksremoveObjectForKey:url];
}
});
for(NSDictionary*callbacksincallbacksForURL) {
SDWebImageDownloaderCompletedBlockcallback = callbacks[kCompletedCallbackKey];
if(callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader*sself = wself;
if(!sself)return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacksremoveObjectForKey:url];
});
}];