源碼解析ios開發SDWebImage方法
引言
在著手寫第二篇的時候,發現這個SDWebimage確實吧NSOperation用的太美瞭。確實可能幫你理解NSOperation
和NSOperationQueue
,當然還有Block的隊列。還有一個GCD
。
各位看官在看的時候可以著重的看看他的operatinQueue
的隊列。看看是怎麼添加到隊列的以及是怎麼移除隊列。在後面的文章就會提到他是怎麼執行的。 還要註重看的就是以前用的NSURLConnection
而現在用的NSURLSession
下來瞭
最近在面試,發現有人問這個組件,所有讀一讀,應付一下面試吧!!
源碼解析
廢話不多說看源碼。
- 1:在組件中提供瞭很多類似這樣的方法
- (void)sd_setImageWithURL:(nullable NSURL *)url; - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- 2:於是乎所有的方法都會調用下面的這個方法
- (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
- 3:下面具體閱讀代碼 第一行執行的代碼
//********1: 所有的設置控件圖片都是經過該方法******* NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); //********2: 取消當前控件正在operations的隊列******* [self sd_cancelImageLoadOperationWithKey:validOperationKey];
解析:
1.在第一行創建瞭validOperationKey
的operationKey,是以當前擴展的類名命名。
2.執行sd_cancelImageLoadOperationWithKey
方法
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key { // Cancel in progress downloader from queue /** * 在該對象中,用runtime手動的添加一個字典屬性。 ### 說一下這裡的operationDictionary ### 該字典的value是下載的操作,然而key是對視圖和操作來做的標識字符串 */ SDOperationsDictionary *operationDictionary = [self operationDictionary]; /* * 在生成字典中的都是新的,所有都沒有數據 */ id operations = operationDictionary[key]; if (operations) { if ([operations isKindOfClass:[NSArray class]]) { for (id <SDWebImageOperation> operation in operations) { if (operation) { [operation cancel]; } } } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ [(id<SDWebImageOperation>) operations cancel]; } [operationDictionary removeObjectForKey:key]; } }
字典操作
在看一下去字典操作[self operationDictionary]
- (SDOperationsDictionary *)operationDictionary { /** ### 這裡有一個疑問? 這樣創建出來的字典每一次都是新的 雖然用瞭 static char loadOperationKey,但是沒一次創建的地址都是新的,並且該字典還是空的。 創建完成之後都直接返回瞭。不知道每次創建的都是新的並且還是空的字典,這樣的開銷會很大的??!!!,也希望各位看官給予解答啊??? */ SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); // 創建成功返回該字典,直接把該字典當做屬性返回 if (operations) { return operations; } operations = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return operations; }
看到這裡我們在返回繼續看 這個函數的代碼有點長,耐心看完啊。。。
- (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 { //********1: 所有的設置控件圖片都是經過該方法******* NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); /**2: 取消當前控件正在operations的隊列 * 至於為什麼在該控件下載前都要清空該控件對應的所有的下載隊列? * 可能是,如果要給控件賦值新的圖片的話,之前所有的操作都和當前的操作無關,所有就取消吧 *******/ [self sd_cancelImageLoadOperationWithKey:validOperationKey]; objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); /**3:設置占位圖片*/ /**這裡的意思就是options參數不是SDWebImageDelayPlaceholder,就執行以下操作 */ if (!(options & SDWebImageDelayPlaceholder)) { /** * 註意這的裡宏定義 * #ifndef dispatch_main_async_safe * #define dispatch_main_async_safe(block)\ * if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\ * block();\ * } else {\ * dispatch_async(dispatch_get_main_queue(), block);\ * } * #endif 可以看出都把block方法主線程中去執行。 應為在系統裡,隻能有一個線程去執行UI的更新,那就是主線程。 如果能在其他線程更新,那這世界就亂瞭,像瞭解 更仔細的問題可以關註www.osjoin.com查看是為什麼! */ dispatch_main_async_safe(^{ /**設置占位圖placeholder*/ [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock]; }); } /**4:菊花提示*/ if (url) { // check if activityView is enabled or not if ([self sd_showActivityIndicatorView]) { [self sd_addActivityIndicator]; } __weak __typeof(self)wself = self; /**5:下載圖片 * 進入下載圖片最重要的函數也是核心的函數瞭 這個函數關聯的函數較多,先簡單過一下。 */ id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { __strong __typeof (wself) sself = wself; [sself sd_removeActivityIndicator]; if (!sself) { return; } dispatch_main_async_safe(^{ if (!sself) { return; } if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; /**將行的下操作放到uiview的下載隊列中的自定義的字典中去*/ [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); } }); } }
看一下調用下載函數前的實例化過程
這個loadImageWithURL函數在SDWebImageManager,並且是用單列調用,
+ (nonnull instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (nonnull instancetype)init { /** ###此處有其他重要配置,下一篇文章解讀 ###此處有其他重要配置,下一篇文章解讀 ###此處有其他重要配置,下一篇文章解讀 */ SDImageCache *cache = [SDImageCache sharedImageCache]; SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; return [self initWithCache:cache downloader:downloader]; } - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { if ((self = [super init])) { _imageCache = cache; _imageDownloader = downloader; /**這裡的failedURLS是NSSet也就沒重復的屬性*/ _failedURLs = [NSMutableSet new]; _runningOperations = [NSMutableArray new]; } return self; }
上面都是初始化的操作,然後看下面的函數
- (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 /*! 如果轉換失敗,url為nil 返回 */ if (![url isKindOfClass:NSURL.class]) { url = nil; } /**5.1:搞一個新的下載隊列*/ /*! 這裡就又__block和__weak的用法區別 這裡簡單說說一下 1:__block,在block函數裡可以修改和閱讀 2:__weak可以避免循環引用,在給他設置新數據的時候,設置方法既不保留新值,也不釋放舊值 */ /*! 說一下SDWebImageCombinedOperation 他擁有瞭一個緩存隊列和一個能取消執行的block,並且還遵守瞭SDWebImageOperation一個協議 */ __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __weak SDWebImageCombinedOperation *weakOperation = operation; /**5.2判斷URL是否是下載失敗的url*/ BOOL isFailedUrl = NO; if (url) { /*! 創建一個互斥鎖防止有其他線程同時修改該對象 */ @synchronized (self.failedURLs) { isFailedUrl = [self.failedURLs containsObject:url]; } } /** * 5.3url不存在或者下載失敗的url 則不在繼續下載隊列 * 返回一個completedBlock */ 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; } /**5.4把url添加在下載隊列 把operation加入到self.runningOperations的數組裡面, 並創建一個互斥線程鎖來保護這個操作 */ @synchronized (self.runningOperations) { [self.runningOperations addObject:operation]; } /*! 獲取image的url對應的key */ NSString *key = [self cacheKeyForURL:url]; /**5.5快速查找***緩存*****/ operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { /** * 這裡的狀態改變,在sd_cancelImageLoadOperationWithKey方法中調用代理函數時會改變該狀態cancel * 如果該隊列是取消狀態,直接從下載隊列中移除,此處有一個安全鎖 */ if (operation.isCancelled) { [self safelyRemoveOperationFromRunning:operation]; return; } /**下載完成之後執行圖片轉換處理和緩存操作**/ //條件1:在緩存中沒有找到圖片或者options選項裡面包含瞭SDWebImageRefreshCached(這兩項都需要進行請求網絡圖片的) //條件2:代理允許下載,SDWebImageManagerDelegate的delegate不能響應imageManager:shouldDownloadImageForURL:方法或者能響應方法且方法返回值為YES.也就是沒有實現這個方法就是允許的,如果實現瞭的話,返回YES才是允許 if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { //如果在緩存中找到瞭image且options選項包含SDWebImageRefreshCached,先在主線程完成一次回調,使用的是緩存中找的圖片 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. /** 如果在緩存中找到瞭image但是設置瞭SDWebImageRefreshCached選項,傳遞緩存的image,同時嘗試重新下載它來讓NSURLCache有機會接收服務器端的更新 dispatch_main_async_safe(^{ if (operation && !operation.isCancelled && completionBlock) { completionBlock(image, data, error, cacheType, finished, url); } }); */ [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; } // 如果沒有在緩存中找到image 或者設置瞭需要請求服務器刷新的選項,則仍需要下載 // 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 // 如果image已經被緩存但是設置瞭需要請求服務器刷新的選項,強制關閉漸進式選項 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing // 如果image已經被緩存但是設置瞭需要請求服務器刷新的選項,忽略從NSURLCache讀取的image downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } /**如果在緩存和硬盤上都沒查到url對應的圖片 ***則進行圖片下載 */ /*! 進入下載操作就是2.2中的操作瞭*/ 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 //如果我們調用completedBlock,這個block會和另外一個completedBlock爭奪一個對象,因此這個block被調用後會覆蓋新的數據 } 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) { //將失敗的url添加到failedURLS的set中去 @synchronized (self.failedURLs) { [self.failedURLs addObject:url]; } } } else { //如果有重試狀態,將url從失敗列表中移除 if ((options & SDWebImageRetryFailed)) { @synchronized (self.failedURLs) { [self.failedURLs removeObject:url]; } } BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); //options包含瞭SDWebImageRefreshCached選項,且緩存中找到瞭image且沒有下載成功 if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if ( //圖片下載成功並且 設置瞭需要變形Image的選項且變形的代理方法已經實現 downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) &&[self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)] ) { /** * dispatch_get_global_queue創建的一個//全局隊列異步隊列執行 */ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ //調用代理方法完成圖片transform 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 //對已經transform的圖片進行緩存 [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 { //如果沒有圖片transform的需求並且圖片下載完成且圖片存在就直接緩存 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]; } }]; /**取消的回調*/ operation.cancelBlock = ^{ [self.imageDownloader cancel:subOperationToken]; __strong __typeof(weakOperation) strongOperation = weakOperation; [self safelyRemoveOperationFromRunning:strongOperation]; }; //在緩存中找到圖片(代理不允許下載 或者沒有設置SDWebImageRefreshCached選項 滿足至少一項) } 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]; } }]; return operation; }
這個函數就進入瞭SDWebimage緩存的策略瞭。
先說一下他的這一個策略緩存。
*1:大傢都是SDWebiamge都是先從緩存上查找,如果有就直接顯示
*2:如果不存在就在沙盒中查找
- *2.1如果存在,則把沙盒中的圖片添加到imageCache中,取出顯示
- *2.2 如果不存在在顯示占位圖,根據URL在operationCache是否存在下載操作
*2.2.1 如果存在,說明該圖片正在下載。
*2.2.2如果不存在,創建圖片下載操作,放到operationCache中
- * 2.3 下載完成,將當前操作隊列從operationCache中移除。並將下載的圖片的添加在imageCache中。顯示
先慢慢體會一下。。。(停留30秒)
開始進入查找函數
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock { /**從緩存中查找圖片開始*/ /*! 檢查key是否為空(URL) */ 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 isGIF]) { diskData = [self diskImageDataBySearchingAllPathsForKey:key]; } /**如果有直接返回在view上顯示*/ if (doneBlock) { doneBlock(image, diskData, SDImageCacheTypeMemory); } return nil; } /**如果內存上沒有數據,則在硬盤上查找,如果找到瞭該圖片,就放到緩存上用doneBlock完成回調**/ /*! 這一塊創建瞭異步隊列 這裡的self.ioQueue是這樣定義的 ****@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue; ****這裡開始瞭串行的隊列去處理硬盤上的緩存 */ NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ /**如果是取消的 就直接返回*/ if (operation.isCancelled) { // do not call the completion if cancelled return; } /*! 開瞭手動釋放池 */ @autoreleasepool { /**從磁盤中讀取圖片*/ /*! 根據url去硬盤上查找數據 */ NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key]; UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.config.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); /**如果在硬盤中讀取到圖片,則把沙盒中的圖片放到Cache中*/ //self.memCache是NSCache創建的一個對象 [self.memCache setObject:diskImage forKey:key cost:cost]; } if (doneBlock) { /*! 在主線程放回數據 */ dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, diskData, SDImageCacheTypeDisk); }); } } }); return operation; }
快速查找緩存的方法回調
看完該函數以後在回到上面的看這個快速查找緩存的方法回調
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { if (operation.isCancelled) { @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } return; } if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { //如果在緩存中找到瞭image且options選項包含SDWebImageRefreshCached,先在主線程完成一次回調,使用的是緩存中找的圖片 if (image && options & SDWebImageRefreshCached) { dispatch_main_sync_safe(^{ // 如果在緩存中找到瞭image但是設置瞭SDWebImageRefreshCached選項,傳遞緩存的image,同時嘗試重新下載它來讓NSURLCache有機會接收服務器端的更新 completedBlock(image, nil, cacheType, YES, url); }); } // 如果沒有在緩存中找到image 或者設置瞭需要請求服務器刷新的選項,則仍需要下載 SDWebImageDownloaderOptions downloaderOptions = 0; //開始各種options的判斷 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 (image && options & SDWebImageRefreshCached) { // 如果image已經被緩存但是設置瞭需要請求服務器刷新的選項,強制關閉漸進式選項 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // 如果image已經被緩存但是設置瞭需要請求服務器刷新的選項,忽略從NSURLCache讀取的image downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } //創建下載操作,先使用self.imageDownloader下載 id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (!strongOperation || strongOperation.isCancelled) { // Do nothing if the operation was cancelled //如果操作取消瞭,不做任何事情 // 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 //如果我們調用completedBlock,這個block會和另外一個completedBlock爭奪一個對象,因此這個block被調用後會覆蓋新的數據 } else if (error) { //進行完成回調 dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(nil, error, SDImageCacheTypeNone, finished, url); } }); //將url添加到失敗列表裡面 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]; } } } else { //如果設置瞭下載失敗重試,將url從失敗列表中去掉 if ((options & SDWebImageRetryFailed)) { @synchronized (self.failedURLs) { [self.failedURLs removeObject:url]; } } BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); //options包含瞭SDWebImageRefreshCached選項,且緩存中找到瞭image且沒有下載成功 if (options & SDWebImageRefreshCached && image && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block // 圖片刷新遇到瞭NSSURLCache中有緩存的狀況,不調用完成回調。 } //圖片下載成功並且 設置瞭需要變形Image的選項且變形的代理方法已經實現 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), ^{ //調用代理方法完成圖片transform UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; //對已經transform的圖片進行緩存 [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk]; } //主線程執行完成回調 dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url); } }); }); } //如果沒有圖片transform的需求並且圖片下載完成且圖片存在就直接緩存 else { if (downloadedImage && finished) { [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } //主線程完成回調 dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url); } }); } } if (finished) { // 從正在進行的操作列表中移除這組合操作 @synchronized (self.runningOperations) { if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } } } }]; //設置組合操作取消得得回調 operation.cancelBlock = ^{ [subOperation cancel]; @synchronized (self.runningOperations) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } } }; } //處理其他情況 //case1.在緩存中找到圖片(代理不允許下載 或者沒有設置SDWebImageRefreshCached選項 滿足至少一項) else if (image) { //完成回調 dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !strongOperation.isCancelled) { completedBlock(image, nil, cacheType, YES, url); } }); //從正在進行的操作列表中移除組合操作 @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } } //case2:緩存中沒有紮到圖片且代理不允許下載 else { //主線程執行完成回調 dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !weakOperation.isCancelled) { completedBlock(nil, nil, SDImageCacheTypeNone, YES, url); } }); //從正在執行的操作列表中移除組合操作 @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } } }];
總結一下函數調用
1.先調用
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
2.設置圖片
- (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
- 2.1 取消該控件對應的之前的所有的下載操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
- 2.2 開始根據圖片的url做為key去查找
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock
2.2.1 查找內存和硬盤上的緩存
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- 2.3 創建下載隊列下載圖片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- 2.4 最後將進行的操作,放到view對應的opationsDicaionary的字典中。記錄當前的操作隊列
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key
以上就是源碼解析ios開發SDWebImage方法的詳細內容,更多關於ios SDWebImage方法的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- iOS GCD之dispatch_group_enter和dispatch_group_leave使用
- Swift如何優雅的進行解包
- iOS實現逐幀動畫做loading視圖
- iOS block循環引用詳解及常見誤區
- Objective-C優雅使用KVO觀察屬性值變化