本文转载请注明出处 —— polobymulberry-博客园html
咱们来到SDWebImageDownloader.m文件中,找到downloadImageWithURL函数。发现代码不是很长,那就一行行读。毕竟这个函数大概作什么咱们是知道的。这个函数大概就是建立了一个SDWebImageSownloader的异步下载器,根据给定的URL下载image。ios
先映入眼帘的是下面两行代码,简单地开开胃:git
// 封装了异步下载图片操做 __block SDWebImageDownloaderOperation *operation; __weak __typeof(self)wself = self;
接着又是一个函数直接到底:addProgressCallback。这是SDWebImageDownloader的私有函数,因此直接一点点看它实现。github
// 这里的url不能为空,下面会解释。若是为空,completedBlock中image、data和error直接传入nil if (url == nil) { if (completedBlock != nil) { completedBlock(nil, nil, nil, NO); } return; }
之因此url不能为空,是由于这个url要做为NSDictionary变量的key值,因此不能为空。而这个NSDictionary变量就是URLCallbacks。咱们从名称大概能够猜到,这个NSDictionary应该是存储每一个url对应的callback(本质是由于一个url基本上对应一个网络请求,而每一个网络请求就是一个SDWebImageDownloaderOperation,而这个SDWebImageDownloaderOperation初始化是使用initWithRequest进行的,initWithRequest须要提供这些callbacks)。那对应的callback函数都有哪些呢?web
咱们先找到URLCallbacks的赋值语句:编程
self.URLCallbacks[url] = callbacksForURL;
那callbacksForURL又是什么?看上面缓存
NSMutableArray *callbacksForURL = self.URLCallbacks[url]; NSMutableDictionary *callbacks = [NSMutableDictionary new]; if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; [callbacksForURL addObject:callbacks];
注意到callbacksForURL是一个NSMutableArray类型,那它其中对应的每一个object存储的是什么呢?看addObject:callbacks,原来是callbacks。那callbacks又是什么?竟然是一个NSMutableDictionary类型。并且存储了对应的progressBlock和completedBlock。这下咱们就明白了其中的关系,如图:安全
这个函数还有一处要注意,就是若是当前url是第一次请求,也就是说对应的URLCallbacks[url]为空,那就新建一个,同时置first为YES,就是说这是第一次建立该url的callbacks。并且还会调用createCallback,至关于第一次初始化过程。服务器
另外整个代码是放在下面的dispatch_barrier_sync中:cookie
dispatch_barrier_sync(self.barrierQueue, ^{ //... });
由于此函数可能会有多个线程同时执行(由于容许多个图片的同时下载),那么就有可能会有多个线程同时修改URLCallbacks,因此使用dispatch_barrier_sync来保证同一时间只有一个线程在访问URLCallbacks。而且此处使用了一个单独的queue--barrierQueue,而且这个queue是一个DISPATCH_QUEUE_CONCURRENT类型的。也就是说,这里虽然容许你针对URLCallbacks的操做是并发执行的,可是由于使用了dispatch_barrier_sync,因此你必须保证以前针对URLCallbacks的操做要完成才能执行下面针对URLCallbacks的操做。
注意:我发现使用barrierQueue的都是dispatch_barrier_sync、dispatch_barrier_async、dispatch_sync,我就纳闷了,这些有用到并发的东西吗?为何不直接使用DISPATCH_QUEUE_SERIAL。求大神告知!下面讨论区一楼和二楼有具体讨论。
总的来讲,上面那个addProgressCallback函数主要就是生成了每一个url的callbacks,而且以URLCallbacks形式传递给别人。具体咱们回到downloadImageWithURL中再看。
回到downloadImageWithURL函数中的addProgressCallback中,看到它具体的createCallback实现。代码不是很长。也是按顺序看:
NSTimeInterval timeoutInterval = wself.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; }
downloadTimeOut表示的下载超时的限定时间,默认是15秒。
而后再往下看就傻眼了,以前对iOS的网络部分一窍不通啊。没办法,硬着头皮,一点点死扣吧。
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
首先要知道initWithURL函数是作什么的?看看注释,大概明白了。就是根据url,缓存策略(cachePolicy)和超时限定时间(timeoutInterval)来产生一个NSURLRequest。这里比较麻烦的是cachePolicy,就是告诉这个request(请求)如何缓存结果:
(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData)
接下来就是设置request的一些属性了(能够看出此处使用的实HTTP协议):
// 若是设置HTTPShouldHandleCookies为YES,就处理存储在NSHTTPCookieStore中的cookies。 // HTTPShouldHandleCookies表示是否应该给request设置cookie并随request一块儿发送出去。 request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); // HTTPShouldUsePipelining表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。 // 若是为YES表示能够,NO表示必须等receiver收到先前的回复才能发送下个信息。 request.HTTPShouldUsePipelining = YES; // 若是你设置了SDWebImageDownloader的headersFilter,就是用你自定义的方法,来设置HTTP的header field。 // 若是没有自定义,就是用SDWebImage提供的HTTPHeaders。 // 简单看下HTTPHeader的初始化部分(若是下载webp图片,须要的header不同): // #ifdef SD_WEBP // _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy]; // #else // _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy]; // #endif if (wself.headersFilter) { request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]); } else { request.allHTTPHeaderFields = wself.HTTPHeaders; }
有了NSURLRequest,接着使用了initWithRequest来初始化一个operation。细节暂且不看,直接跳过,后面的看完再来好好研究。先看下面:
operation.shouldDecompressImages = wself.shouldDecompressImages;
这个简单,就是说要不要解压缩图片。解压缩已经下载的图片或者在缓存中的图片,能够提升性能,可是会耗费不少空间,缺省状况下是要解压缩图片。
if (wself.urlCredential) { operation.credential = wself.urlCredential; } else if (wself.username && wself.password) { operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; }
urlCredential是一个NSURLCredential类型。
web 服务能够在返回 http 响应时附带认证要求的challenge,做用是询问 http 请求的发起方是谁,这时发起方应提供正确的用户名和密码(即认证信息),而后 web 服务才会返回真正的 http 响应。 收到认证要求时,NSURLConnection 的委托对象会收到相应的消息并获得一个 NSURLAuthenticationChallenge 实例。该实例的发送方遵照 NSURLAuthenticationChallengeSender 协议。为了继续收到真实的数据,须要向该发送方向发回一个 NSURLCredential 实例。
若是已经有了credential,那就直接赋值。若是没有,就用用户名(username)和密码(password)新构建一个:
[NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
其中NSURLCredentialPersistenceForSession表示在应用终止时,丢弃相应的 credential 。
接着是设置该operation的优先级,毕竟operation对应一个NSOperation。
if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; }
这个简单,就是优先级设定,通常来讲,优先级越高,执行越早。
而后就是添加到NSOperationQueue中,这个downloadQueue一看就知道确定是NSOperationQueue,代码以下:
[wself.downloadQueue addOperation:operation];
最后是处理operation的执行顺序:
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // 若是执行顺序为LIFO(last in first out,后进先出,栈结构) // 就将新添加的operation做为最后一个operation的依赖,就是说,要执行最后一个operation,必须先执行完新添加的operation,这就实现了栈结构。 [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; }
刚才说的都是对operation的一些属性设置。如今能够回到operation建立的那个函数initWithRequest中了。顺便提一句,initWithRequest是SDWebImageDownloaderOperation函数,因此前面[wself.operationClass]返回的是SDWebImageDownloaderOperation(不相信的话,请搜索setOperationClass)。这也是一个编程技巧,把Class类型做为属性存起来。
// 先看看这个函数声明和注释,返回的是SDWebImageDownloaderOperation。 // 参数须要request,不过这个上面的代码已经建立好了,而options使用的是downloadImageWithURL传入的options // 真正须要在传递给此函数的就剩下三个block了:progressBlock、completedBlock、cancelBlock - (id)initWithRequest:(NSURLRequest *)request options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock cancelled:(SDWebImageNoParamsBlock)cancelBlock;
先看progress:
progress:^(NSInteger receivedSize, NSInteger expectedSize) { SDWebImageDownloader *sself = wself; if (!sself) return; __block NSArray *callbacksForURL; dispatch_sync(sself.barrierQueue, ^{ callbacksForURL = [sself.URLCallbacks[url] copy]; }); for (NSDictionary *callbacks in callbacksForURL) { dispatch_async(dispatch_get_main_queue(), ^{ SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; if (callback) callback(receivedSize, expectedSize); }); } }
其中主要难点在下面这段代码:
dispatch_sync(sself.barrierQueue, ^{ callbacksForURL = [sself.URLCallbacks[url] copy]; });
注意此处使用了同步方法dispatch_sync,也就是说,callbacksForURL这条赋值语句是放在barrierQueue线程执行的,并且此时会阻塞当前线程。咱们以前提到过,barrierQueue是为了保证同一时刻只有一个线程对URLCallbacks进行操做。说实话,我不是很明白这里为何要使用dispatch_sync,为何不用dispatch_barrier_sync?但愿大神能够告知缘由。(此处我回头想了下,多是由于对于同一个图片下载任务,会不停地调用progressBlock函数,这个callbacksForURL的赋值语句多是在同一个图片下载任务的不一样的线程(一个图片每次下载到新数据后调用progressblock)中执行的,可是你必需要保证前一部分数据下载任务完成,才能执行后一部分数据的下载任务,此处须要同步,因此使用dispatch_sync,此处单独使用一个barrierQueue,还能够防止dispatch_sync形成死锁)。
跟着的for循环就好理解了,直接从callbacks中索引到progressBlock,放入主线程中进行下载,固然,下载过程当中确定要知道已经下载了多少(receivedSize)和预期下载的大小(expectedSize)。由于这个block是不停调用,只要有新的数据到达就调用,直到下载完成,因此这两个参数仍是必备的,判断是否下载完成。
下面的completedBlock:
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { SDWebImageDownloader *sself = wself; if (!sself) return; __block NSArray *callbacksForURL; dispatch_barrier_sync(sself.barrierQueue, ^{ callbacksForURL = [sself.URLCallbacks[url] copy]; if (finished) { [sself.URLCallbacks removeObjectForKey:url]; } }); for (NSDictionary *callbacks in callbacksForURL) { SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey]; if (callback) callback(image, data, error, finished); } }
这里使用的是dispatch_barrier_sync。不一样图片的下载任务会异步完成,因此必要保证以前其余图片下载完成,并执行完completedBlock内的对URLCallbacks的操做,才能接着运行。由于只要等以前的进程完成,并不须要关心以前的进程是否是同步执行,因此使用的是dispatch_barrier_sync。其余逻辑部分,很简单,就不赘述了。
最后是cancelBlock:
cancelled:^{ SDWebImageDownloader *sself = wself; if (!sself) return; dispatch_barrier_async(sself.barrierQueue, ^{ [sself.URLCallbacks removeObjectForKey:url]; }); }
由于取消了,因此直接把url从URLCallbacks中移除。可是此处同步方案又是用dispatch_barrier_async。其实我以为在同一个queue中,使用dispatch_barrier_async仍是使用dispatch_barrier_sync并无什么区别。由于都是要等以前的执行完成。(不过dispatch_barrier_async表示的是先等以前的执行完成,而后把该barrier放入queue中,而不是等待barrier中代码执行结束,而dispat_barrier_sync表示须要等待barrier中代码执行结束)。
以前这个系列的博客都是为了构造一个operation(NSOperation),而且也放到downloadQueue(NSOperationQueue)。可是咱们还须要点火启动这个operation。
咱们实现了NSOperation的子类,那么要让其运行起来,要么实现main(),要么实现start()。这里SDWebImageDownloaderOperation选择实现了start()。咱们先一步步看看start()实现:
先是一个线程线程同步锁(以self做为互斥信号量):
@synchronized (self) { // ... }
此处到底写了什么代码,竟然须要同步,并且仍是以加锁的方式?
首先是判断当前这个SDWebImageDownloaderOperation是否取消了,若是取消了,即认为该任务已经完成,而且及时回收资源(即reset)。
这里简单介绍下NSOperation的三个重要的状态,若是你使用了NSOperation,就须要手动管理这三个重要的状态:
isExecuting
表明任务正在执行中isFinished
表明任务已经执行完成isCancelled
表明任务已经取消执行if (self.isCancelled) { self.finished = YES; [self reset]; // 资源回收,资源所有置为nil,自动回收 return; }
而后是一段宏中的代码,这段代码主要是考虑到app进入后台发生的事,虽然代码很简单,可是有些技巧仍是须要学习的:
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; } }]; }
由于要使用beginBackgroundTaskWithExpirationHandler,因此须要使用[UIApplication sharedApplication],由于是第三方库,因此须要使用NSClassFromString获取到UIApplication。这里须要说起的就是shouldContinueWhenAppEntersBackground,也就是说下载选项中须要设置SDWebImageDownloaderContinueInBackground。
注意beginBackgroundTaskWithExpirationHandler并非意味着当即执行后台任务,它只是至关于注册了一个后台任务,函数后面的handler block表示程序在后台运行时间到了后,要运行的代码。这里,后台时间结束时,若是下载任务还在进行,就取消该任务,而且调用endBackgroundTask,以及置backgroundTaskId为UIBackgroundTaskInvalid。
注意此处取消任务的方法cancel是SDWebImageDownloaderOperation从新定义的。
- (void)cancel { @synchronized (self) { if (self.thread) { [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO]; } else { [self cancelInternal]; } } }
这里我比较奇怪为何self.thread存在和不存在是两种取消方式,并且什么状况下self.thread会不存在呢?
具体看cancelInternalAndStop和cancelInternal代码,发现cancelInternalAndStop就多了一行代码:
CFRunLoopStop(CFRunLoopGetCurrent());
由于每一个NSThread都会有一个CFRunLoop(后面的代码会有CFRunLoopRun函数出现),因此若是要取消的话,就得同时stop这个RunLoop。因此cancel函数的逻辑主要就是cancelIntenal函数了。
cancelIntenal函数所作了三件事:
注意到在取消self.connection过程当中,发送了一个SDWebImageDownloadStopNotification的通知。咱们能够看到这个通知注册的地方是在SDWebImageDownloader类的initialize函数:
+ (void)initialize { // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator ) // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import if (NSClassFromString(@"SDNetworkActivityIndicator")) { // .... [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"stopActivity") name:SDWebImageDownloadStopNotification object:nil]; } }
注意到若是你要使用这个SDWebImageDownloadStopNotification通知,须要绑定SDNetworkActivityIndicator,这个貌似是须要单独下载的。固然,你能够修改这部分源代码,换成别的ActivityIndicator。
这里就有疑问了,此时咱们的backgroundTaskId已经注册过了,若是此NSOperation在进入后台运行以前就已经完成任务了,不就应该把这个backgroundTaskId置为UIBackgroundTaskInvalid吗,意思就是告诉系统,任务完成,不须要考虑进不进入后台运行的问题了。确实,在start函数末尾,就是判断若是下载任务完成(无论有没有下载成功),就将backgroundTaskId置为UIBackgroundTaskInvalid。
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; }
回到上面代码接着看:
self.executing = YES; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; self.thread = [NSThread currentThread];
注册事后台代码后,接着就是要正式运行了。因此先要置executing属性为YES。而后就是关键的connection了。connection是一个NSURLConnection类型的属性。这里咱们能感受到,真正的下载图片的网络处理部分就是利用了NSURLConnection。此处使用的self.request就是上面提到的那个NSMutableURLRequest(在SDWebImageDownloader.m中的downloadImageWithURL函数中生成的)。其实咱们如今应该看下SDWebImageDownloaderOperation中实现的NSURLConnectionDataDelegate方法。可是不急,先把start函数中的剩下函数看完。剩下的不是很难,因此先解决。
虽然已经使用init方法构建了一个NSURLConnection,可是真正要启动下载还须要使用NSURLConnection的start方法。
[self.connection start];
接下来就是判断这个connection是否建立成功:
if (self.connection) { // ...... } else { // ...... }
这个if else语句要分一下两个情形讨论:
由于刚connection刚start,因此此处执行的progresBlock的参数为receivedSize=0,expectedSize=NSURLResponseUnknownLength(((long long)-1))。咱们都知道通常除非自定义progressBlock,否则通常progresBlock为nil。因此若是这里用户自定义了progressBlock,可是这是用户定义的行为,为何要将参数设置成这样呢?我不是很清楚,可是用户在设计本身的progressBlock的时候就要留心这个参数问题了,要特地处理expectedSize为NSURLResponseUnknownLength的状况。
接着回到主进程使用SDWebImageDownloadStartNotification,和以前说的SDWebImageDownloadStopNotification有殊途同归之处。读者能够本身查询。
接下来就是调用RunLoop了。这里它以NSFoundation的iOS5.1版本做为分界线进行讨论的,不过二者作的事情都同样,只不过调用函数不一样罢了——都是调用RunLoop直到下载任务终止或者完成。
这是CFRunLoopRunInMode和CFRunLoopRun的源码:
CFRunLoopRunInMode
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); }
CFRunLoopRun
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }
稍微提一下CFRunLoopRun,大概能看出来这是一个while循环,而且是在使用CFRunLoopGetCurrent()来不停地执行当前RunLoop的任务,直到任务被终止或者完成。
你能够这样理解这两个函数关系,CFRunLoopRun就是使用默认mode运行的CFRunLoopRunInMode。至于为何iOS5.1以前的要使用CFRunLoopRunInMode,咱们从其中的注释也能够看出,其实主要是利用CFRunLoopRunInMode的CFTimeInterval seconds参数。
那么执行当前进程的任务到底指什么?具体请看这篇文章--深刻理解RunLoop。简单点说,这里进程主要是响应NSURLConnectionDataDelegate和NSURLConnectionDelegate的各类代理函数。
一般使用 NSURLConnection 时,你会传入一个 delegate,当调用了 [self.connection start] 后,这个delegate 就会不停收到事件回调。因此也就是说等这个connection完成或者终止,才会跳出CFRunLoopRun()。当跳出Runloop后,就要判断NSURLConnection是否是正常完成任务了。若是没有,也就是说self.isFinished == NO。那么就取消该connection,而且调用- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;返回错误信息,打印出错的请求url。总的代码以下:
if (!self.isFinished) { [self.connection cancel]; [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]]; }
调用completedBlock。由于此处是失败了,因此image和data参数为nil,而error从它的NSLocalizedDescriptionKey就能够看出Connection can't be initialized。
其实咱们只剩下了SDWebImageDownloader的downloadImageWithURL中的completedBlock部分还没细说了。
completedBlock也分为三种情形:
什么都不作。由于若是你要在此处调用completedBlock的话,可能会存在和其余的completedBlock产生条件竞争,可能会修改同一个数据。
if (weakOperation.isCancelled) { // ...... }
else if (error) { // ...... }
首先先判断operation是否取消了(检查是否取消要勤快点),没有取消,就调用completedBlock,处理error。
dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(nil, error, SDImageCacheTypeNone, finished, url); } });
随后检查错误类型,确认不是客户端或者服务器端的网络问题,就认为这个url自己问题了。并把这个url放到failedURLs中。
if ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost) { @synchronized (self.failedURLs) { [self.failedURLs addObject:url]; } }
若是使用了SDWebImageRetryFailed选项,那么即便该url是failedURLs,也要从failedURLs移除,并继续执行download:
if ((options & SDWebImageRetryFailed)) { @synchronized (self.failedURLs) { [self.failedURLs removeObject:url]; } }
cacheOnDisk表示是否使用磁盘上的缓存:
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
接着又是一个if else。咱们先大概看看框架:
// image是从SDImageCache中获取的,downloadImage是从网络端获取的 // 因此虽然options包含SDWebImageRefreshCached,须要刷新imageCached, // 并使用downloadImage,不过惋惜downloadImage没有从网络端获取到图片。 if (options & SDWebImageRefreshCached && image && !downloadedImage) { // ...... } // 图片下载成功,获取到了downloadedImage。 // 这时候若是想transform已经下载的图片,就得先判断这个图片是否是animated image(动图), // 这里能够经过downloadedImage.images是否是为空判断。 // 默认状况下,动图是不容许transform的,不过若是options选项中有SDWebImageTransformAnimatedImage,也是容许transform的。 // 固然,静态图片不受此干扰。另外,要transform图片,还须要实现 // transformDownloadedImage这个方法,这个方法是在SDWebImageManagerDelegate代理定义的 else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { // ...... else { // 这个不用解释了 }
接着咱们就能够具体看看每一个判断里面的实现了:
不过咱们先看下transformDownloadedImage的注释:
// 容许在image刚下载完,以及在缓存到内存和disk以前,进行transform。 // 注意:该方法是在一个global queue中调用,为了不阻塞主线程。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // ....... }
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url); } });
// 和上面else if同样,根据一个key将downloadedImage存储到缓存,不过此处不须要从新计算data的 if (downloadedImage && finished) { [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } // operation没被取消,就调用completedBlock dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url); } });
到目前为止,咱们整个代码其实就是为了建立一个NSOperation,而后利用NSURLConnection去下载图片。下面一篇会具体说说NSURLConnection如何下载图片的。