本文轉載請註明出處 —— 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如何下載圖片的。