<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在著手寫第二篇的時候,發現這個SDWebimage確實吧NSOperation用的太美了。確實可能幫你理解NSOperation
和NSOperationQueue
,當然還有Block的佇列。還有一個GCD
。
各位看官在看的時候可以著重的看看他的operatinQueue
的佇列。看看是怎麼新增到佇列的以及是怎麼移除佇列。在後面的文章就會提到他是怎麼執行的。 還要注重看的就是以前用的NSURLConnection
而現在用的NSURLSession
下來了
最近在面試,發現有人問這個元件,所有讀一讀,應付一下面試吧!!
廢話不多說看原始碼。
- (void)sd_setImageWithURL:(nullable NSURL *)url; - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- (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];
解析:
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.2.1 如果存在,說明該圖片正在下載。
*2.2.2如果不存在,建立圖片下載操作,放到operationCache中
先慢慢體會一下。。。(停留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]; } } }];
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- (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
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)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;
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key
以上就是原始碼解析ios開發SDWebImage方法的詳細內容,更多關於ios SDWebImage方法的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45