原始碼閱讀:SDWebImage(十四)——SDWebImageManager

堯少羽發表於2018-07-04

該文章閱讀的SDWebImage的版本為4.3.3。

這個類是載入影像的核心類,它處理了影像快取,影像下載功能的邏輯,為外部提供了簡潔的影像載入方法。

1.公共列舉

/**
 這個位列舉定義了影像載入的可選項
 */
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    /**
     預設情況下,當url無法下載時,url會被列入黑名單,之後再載入該url就不會繼續嘗試。
     設定這個選項可禁用此黑名單,可以再次下載已經下載失敗過的url
     */
    SDWebImageRetryFailed = 1 << 0,

    /**
     預設情況下,在UI互動期間影像下載就開始了。
     設定這個選項可禁用此功能,比如在UIScrollView滾動變慢時就會延遲影像下載。
     */
    SDWebImageLowPriority = 1 << 1,

    /**
     預設情況下,影像下載完成後,會被快取到記憶體和硬碟中。
     設定這個選項可禁用硬碟快取,僅快取在記憶體中。
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

    /**
     預設情況下,影像僅在完全下載後顯示。
     設定這個選項可啟用漸進式下載,影像在瀏覽器中下載時逐步顯示。
     */
    SDWebImageProgressiveDownload = 1 << 3,

    /**
     預設情況下,同一個url不會重複下載,下載成功後再次載入該url會直接從快取中獲取影像。
     但有一種情況是,該url對應的伺服器資源發生了變化,但是該url沒有變。
     設定這個選項就可以重新下載該url並重新整理對應的快取。
     */
    SDWebImageRefreshCached = 1 << 4,

    /**
     預設情況下,應用切到後臺後就停止下載。
     設定這個選項可向系統申請額外的時間來實現後臺下載。
     */
    SDWebImageContinueInBackground = 1 << 5,

    /**
     是否使用預設的cookie處理請求,設定這個選項就為是
     */
    SDWebImageHandleCookies = 1 << 6,

    /**
     是否允許通過不受信任的SSL證書,,設定這個選項就為是
     */
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    /**
     預設情況下,影像是按順序下載。
     設定這個選項可使該影像下載擁有高優先順序,以排在較前的順序下載
     */
    SDWebImageHighPriority = 1 << 8,
    
    /**
     預設情況下,在載入影像時會先載入佔點陣圖。 
     設定這個選項可延遲載入佔點陣圖,直到影像載入完成。
     */
    SDWebImageDelayPlaceholder = 1 << 9,

    /**
     預設情況下,處理動圖時不會呼叫imageManager:transformDownloadedImage:withURL:這個代理方法。
     設定這個選項可在處理動圖時也呼叫該代理方法。
     */
    SDWebImageTransformAnimatedImage = 1 << 10,
    
    /**
     預設情況下,影像下載完成後會被自動新增到imageView上。
     但有一種情況是,我們想在影像被新增到imageView上之前對影像做一些處理。
     設定這個選項可使影像不自動新增到imageView,而手動新增。
     */
    SDWebImageAvoidAutoSetImage = 1 << 11,
    
    /**
     預設情況下,影像會根據其原始大小進行解碼。
     設定這個選項可壓縮影像大小。
     但是如果也同時設定了SDWebImageProgressiveDownload這個選項,壓縮影像的功能就會失效。
     */
    SDWebImageScaleDownLargeImages = 1 << 12,
    
    /**
     預設情況下,當記憶體中有快取時,就不會再去查詢硬碟中的快取。
     設定這個選項可強制同時查詢硬碟中的快取。
     但是建議和SDWebImageQueryDiskSync一起使用,以確保影像在同一個runloop中載入。
     */
    SDWebImageQueryDataWhenInMemory = 1 << 13,
    
    /**
     預設情況下,是同步查詢記憶體快取,非同步查詢硬碟快取。 設定這個選項可強制同步查詢硬碟快取,以確保在同一個runloop中載入影像。
     */
    SDWebImageQueryDiskSync = 1 << 14,
    
    /**
     預設情況下,當沒有快取時,才從網路下載影像。
     設定這個選項可強制從網路下載影像。
     */
    SDWebImageFromCacheOnly = 1 << 15,
    /**
     預設情況下,使用SDWebImageTransition新增的過渡動畫只適用於從網路下載的影像。
     設定這個選項可使過渡動畫也強制適用於從快取獲取的影像
     */
    SDWebImageForceTransition = 1 << 16
};
複製程式碼

2.公共型別定義

/**
 用於外部分類中的完成回撥block
 */
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
複製程式碼
/**
 用於該類內部中的完成回撥block
 */
typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL);
複製程式碼
/**
 用於將url處理成快取用的key的方法,可以刪除url中的查詢欄位
 */
typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable url);
複製程式碼
/**
 用於將影像快取到硬碟的解碼演算法
 */
typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);
複製程式碼

3.SDWebImageManagerDelegate代理

/**
 如果沒找到url對應的快取,url需要從網路下載時會呼叫這個代理方法
 */
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
複製程式碼
/**
 如果url對應的影像下載失敗了,就會呼叫這個代理方法
 */
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error;
複製程式碼
/**
 影像下載完成以後,如果想轉換圖片就呼叫這個代理方法
 */
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
複製程式碼

4.公共屬性

/**
 代理
 */
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
複製程式碼
/**
 影像快取物件
 */
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
複製程式碼
/**
 影像下載器
 */
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
複製程式碼
/**
 將url處理成key的方法
 */
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
複製程式碼
/**
 影像解碼的方法
 */
@property (nonatomic, copy, nullable) SDWebImageCacheSerializerBlock cacheSerializer;
複製程式碼

5.公共方法

/**
 獲取單例物件的方法
 */
+ (nonnull instancetype)sharedManager;
複製程式碼
/**
 以指定影像快取物件和影像下載器初始化物件
 */
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
複製程式碼
/**
 載入url影像的方法,如果沒有快取就從網路下載
 */
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;
複製程式碼
/**
 以指定的url快取指定影像
 */
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
複製程式碼
/**
 取消當前所有操作
 */
- (void)cancelAll;
複製程式碼
/**
 檢視當前是否有操作正在進行
 */
- (BOOL)isRunning;
複製程式碼
/**
 非同步查詢指定url是否有快取
 */
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
複製程式碼
/**
 非同步查詢指定url在硬碟中是否有快取
 */
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
複製程式碼
/**
 獲取指定url的快取key
 */
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
複製程式碼

6.SDWebImageCombinedOperation類

6.1.屬性

/**
 取消
 */
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
複製程式碼
/**
 影像下載token
 */
@property (strong, nonatomic, nullable) SDWebImageDownloadToken *downloadToken;
複製程式碼
/**
 快取操作物件
 */
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
複製程式碼
/**
 影像管理者物件
 */
@property (weak, nonatomic, nullable) SDWebImageManager *manager;
複製程式碼

6.2.方法

- (void)cancel {
    // 加鎖
    @synchronized(self) {
        // 記錄取消狀態
        self.cancelled = YES;
        // 如果正在快取就取消快取,並將屬性置空
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        // 如果正在下載就取消下載
        if (self.downloadToken) {
            [self.manager.imageDownloader cancel:self.downloadToken];
        }
        // 移除掉當前操作
        [self.manager safelyRemoveOperationFromRunning:self];
    }
}
複製程式碼

7.類擴充套件屬性

/**
 儲存影像快取物件
 */
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
複製程式碼
/**
 儲存影像下載物件
 */
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
複製程式碼
/**
 儲存下載失敗的url
 */
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
複製程式碼
/**
 儲存正在進行的操作
 */
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;
複製程式碼

8.實現

8.1.生命週期方法實現

- (nonnull instancetype)init {
    // 建立影像快取物件
    SDImageCache *cache = [SDImageCache sharedImageCache];
    // 建立影像下載物件
    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    // 用萬能方法初始化物件並返回
    return [self initWithCache:cache downloader:downloader];
}
複製程式碼

8.2.私有方法實現

/**
 縮放影像物件
 */
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
    return SDScaledImageForKey(key, image);
}
複製程式碼
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    // 加鎖
    @synchronized (self.runningOperations) {
        // 從集合屬性中移除影像載入操作封裝物件
        if (operation) {
            [self.runningOperations removeObject:operation];
        }
    }
}
複製程式碼
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  error:(nullable NSError *)error
                                    url:(nullable NSURL *)url {
    // 呼叫下面的全能方法
    [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}
複製程式碼
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  image:(nullable UIImage *)image
                                   data:(nullable NSData *)data
                                  error:(nullable NSError *)error
                              cacheType:(SDImageCacheType)cacheType
                               finished:(BOOL)finished
                                    url:(nullable NSURL *)url {
    // 主佇列非同步呼叫
    dispatch_main_async_safe(^{
        // 如果有影像載入操作封裝物件,
        // 並且影像載入操作封裝物件沒被取消
        // 並且有完成回撥block
        if (operation && !operation.isCancelled && completionBlock) {
            // 回撥結果
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });
}
複製程式碼

8.3.公共方法實現

+ (nonnull instancetype)sharedManager {
    // 獲取單例物件
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
複製程式碼
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    // 初始化屬性
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}
複製程式碼
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
    // 如果沒傳url就返回空字串
    if (!url) {
        return @"";
    }

    // 如果設定了url處理方法就呼叫處理方法,否則就返回url的字串格式
    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    } else {
        return url.absoluteString;
    }
}
複製程式碼
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    // 獲取url對應的key
    NSString *key = [self cacheKeyForURL:url];
    
    // 通過快取物件獲取key對應的記憶體快取
    BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
    
    // 如果有記憶體快取
    if (isInMemoryCache) {
        // 主佇列非同步回撥
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completionBlock) {
                completionBlock(YES);
            }
        });
        return;
    }
    
    // 通過快取物件獲取key對應的硬碟快取
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // 回撥
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
複製程式碼
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    // 獲取url對應的key
    NSString *key = [self cacheKeyForURL:url];
    
    // 通過快取物件獲取key對應的硬碟快取
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // 回撥
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
複製程式碼
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // 按成回撥block是必傳的
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // 雖然引數要求傳NSURL型別物件但是如果傳NSStriing型別物件並不會有警告,所以再做一下處理
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // 防止引數url的型別錯誤導致崩潰,例如url的是NSNull型別的
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    // 生成一個影像載入操作的封裝物件
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;
    
    // 定義變數儲存要載入的url是否失敗過
    BOOL isFailedUrl = NO;
    if (url) {
        // 加鎖
        @synchronized (self.failedURLs) {
            // 判斷是否失敗過
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }

    // 如果連結地址不正確,
    // 或者之前載入失敗過並且沒有設定失敗url重試選項,
    /// 就直接回撥錯誤並返回
    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;
    }

    // 加鎖
    @synchronized (self.runningOperations) {
        // 儲存當前操作封裝物件到屬性中
        [self.runningOperations addObject:operation];
    }
    // 獲取url對應的key
    NSString *key = [self cacheKeyForURL:url];
    
    // 建立變數儲存影像快取選項
    SDImageCacheOptions cacheOptions = 0;
    // 根據設定的選項設定影像快取的選項
    if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
    if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
    
    // 通過快取物件查詢url對應的快取
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        __strong __typeof(weakOperation) strongOperation = weakOperation;
        // 如果影像載入操作封裝物件不存在,
        // 或者影像載入操作封裝物件被取消,
        // 就從影像載入操作封裝物件集合屬性中移除並返回
        if (!strongOperation || strongOperation.isCancelled) {
            [self safelyRemoveOperationFromRunning:strongOperation];
            return;
        }
        
        // 建立變數儲存是否要下載影像
        // 想要從網路下載影像必須同時滿足以下條件:
        // 沒有設定只從快取載入的選項
        // 沒找到快取影像,或者設定了需要重新整理快取影像的選項
        // 代理物件沒有實現這個代理方法,或者代理物件實現了這個代理方法並且代理方法返回了YES,意思是在快取中找不到圖片時要從網路上下載
        BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
            && (!cachedImage || options & SDWebImageRefreshCached)
            && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
        // 如果需要下載影像
        if (shouldDownload) {
            // 如果有影像快取並且設定了重新整理圖片快取,就先呼叫完成回撥block
            if (cachedImage && options & SDWebImageRefreshCached) {
                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

            // 建立變數儲存影像下載的選項
            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) {
                // 取消漸進下載
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // 忽視從NSURLCache中獲取快取
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            
            // 開啟下載任務並獲取下載token
            __weak typeof(strongOperation) weakSubOperation = strongOperation;
            strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
                if (!strongSubOperation || strongSubOperation.isCancelled) {
                     // 如果影像載入操作封裝物件不存在,
                     // 或者影像載入操作封裝物件被取消,
                     // 就什麼都不做
                } else if (error) {
                    // 如果下載出錯
                    // 通過完成回撥block回撥錯誤
                    [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url];
                    // 建立變數儲存是否儲存錯誤url
                    BOOL shouldBlockFailedURL;
                    if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
                        // 如果實現了代理就遵循代理方法返回的資料
                        shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
                    } else {
                        // 如果沒有實現代理,
                        // 想要阻止下載失敗的連結就得滿足以下條件:
                        // 不是沒聯網、
                        // 不是被取消、
                        // 不是連線超時、
                        // 不是關閉了國際漫遊、
                        // 不是不允許蜂窩資料連線、
                        // 不是沒有找到host、
                        // 不是無法連線host、
                        // 不是連線丟失
                        shouldBlockFailedURL = (   error.code != NSURLErrorNotConnectedToInternet
                                                && error.code != NSURLErrorCancelled
                                                && error.code != NSURLErrorTimedOut
                                                && error.code != NSURLErrorInternationalRoamingOff
                                                && error.code != NSURLErrorDataNotAllowed
                                                && error.code != NSURLErrorCannotFindHost
                                                && error.code != NSURLErrorCannotConnectToHost
                                                && error.code != NSURLErrorNetworkConnectionLost);
                    }
                    
                    // 如果需要阻止失敗url再次下載,
                    // 就把url新增到黑名單中儲存
                    if (shouldBlockFailedURL) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    // 如果下載成功
                    // 如果設定了重新下載失敗的url選項,
                    // 就把url從黑名單中移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    // 建立變數儲存是否快取到硬碟,並根據設定的選項賦值
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    
                    // 在單例管理物件SDWebImageDownloader中已經實現了圖片的縮放,這裡是用於自定義管理物件以避免額外的縮放
                    if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
                        // 如果當前物件不是SDWebImageManager的單例物件,
                        // 並且設定裡過濾連結程式碼塊,
                        // 並且下載到了圖片。就進行縮放
                        downloadedImage = [self scaledImageForKey:key image:downloadedImage];
                    }

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // 如果設定了重新整理快取選項,
                        // 並且有快取圖,
                        // 並且沒有下載圖,
                        // 就什麼也不需要做
                    } 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), ^{
                            // 呼叫代理方法獲取轉換後的圖片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            
                            // 如果獲取到了轉換後的圖片並且下載完成
                            if (transformedImage && finished) {
                                // 判斷轉換後的圖片是否真的被轉換了
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                // 建立變數儲存快取資料
                                NSData *cacheData;
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                if (self.cacheSerializer) {
                                    // 如果自定義了影像解碼演算法,就按照自定義的演算法來解碼。
                                    cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
                                } else {
                                    // 否則就檢查圖片是否被轉換,
                                    // 如果被轉換過,就不返回影像資料了,
                                    // 如果沒轉換過就返回影像資料
                                    cacheData = (imageWasTransformed ? nil : downloadedData);
                                }
                                // 通過快取物件快取影像
                                [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            
                            // 呼叫完成回撥block回撥結果
                            [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        // 否則
                        // 如果有下載圖並且下載完成
                        if (downloadedImage && finished) {
                            if (self.cacheSerializer) {
                               
                               // 如果自定義了影像解碼演算法
                               // 全域性併發佇列非同步呼叫
                               dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                    // 按照自定義的演算法來解碼獲取影像資料
                                    NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
                                    // 通過快取物件快取影像
                                    [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                                });
                            } else {
                                // 如果沒自定義解碼演算法就直接通過快取物件快取影像
                                [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                        }
                        // 呼叫完成回撥block回撥結果
                        [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }

                // 如果下載完成就把當前影像載入操作封裝物件從集合屬性中移除
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongSubOperation];
                }
            }];
        } else if (cachedImage) {
             // 如果不需要下載,
             // 並且獲取到了快取圖,
             // 呼叫完成回撥block回撥結果
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            // 把當前影像載入操作封裝物件從集合屬性中移除
            [self safelyRemoveOperationFromRunning:strongOperation];
        } else {
            // 如果不需要下載,
            // 並且也沒有快取圖
            // 呼叫完成回撥block回撥結果
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            // 把當前影像載入操作封裝物件從集合屬性中移除
            [self safelyRemoveOperationFromRunning:strongOperation];
        }
    }];
    
    // 返回影像載入操作封裝物件
    return operation;
}
複製程式碼
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url {
    // 必須要傳引數
    if (image && url) {
        // 獲取url對應的key
        NSString *key = [self cacheKeyForURL:url];
        // 通過快取物件進行快取
        [self.imageCache storeImage:image forKey:key toDisk:YES completion:nil];
    }
}
複製程式碼
- (void)cancelAll {
    // 加鎖
    @synchronized (self.runningOperations) {
        // 獲取到當前所有正在執行的操作
        NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
        // 呼叫所有影像載入操作封裝物件的取消方法
        [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
        // 移除獲取到的所有的操作
        [self.runningOperations removeObjectsInArray:copiedOperations];
    }
}
複製程式碼
- (BOOL)isRunning {
    // 建立變數儲存執行狀態,預設為NO
    BOOL isRunning = NO;
    // 加鎖
    @synchronized (self.runningOperations) {
        // 如果當前有正在執行的操作就為YES
        isRunning = (self.runningOperations.count > 0);
    }
    // 返回狀態
    return isRunning;
}
複製程式碼

9.總結

可以看到,由於有良好的框架設計,在實現影像載入操作時的邏輯就很清晰: 由影像快取物件SDImageCache來負責快取的查詢和新增;由影像下載物件SDWebImageDownloader來負責影像的網路下載;由影像載入操作封裝物件SDWebImageCombinedOperation來負責整體影像載入操作的控制。所以,一個良好的架構是非常非常必要和重要的。

原始碼閱讀系列:SDWebImage

原始碼閱讀:SDWebImage(一)——從使用入手

原始碼閱讀:SDWebImage(二)——SDWebImageCompat

原始碼閱讀:SDWebImage(三)——NSData+ImageContentType

原始碼閱讀:SDWebImage(四)——SDWebImageCoder

原始碼閱讀:SDWebImage(五)——SDWebImageFrame

原始碼閱讀:SDWebImage(六)——SDWebImageCoderHelper

原始碼閱讀:SDWebImage(七)——SDWebImageImageIOCoder

原始碼閱讀:SDWebImage(八)——SDWebImageGIFCoder

原始碼閱讀:SDWebImage(九)——SDWebImageCodersManager

原始碼閱讀:SDWebImage(十)——SDImageCacheConfig

原始碼閱讀:SDWebImage(十一)——SDImageCache

原始碼閱讀:SDWebImage(十二)——SDWebImageDownloaderOperation

原始碼閱讀:SDWebImage(十三)——SDWebImageDownloader

原始碼閱讀:SDWebImage(十四)——SDWebImageManager

原始碼閱讀:SDWebImage(十五)——SDWebImagePrefetcher

原始碼閱讀:SDWebImage(十六)——SDWebImageTransition

原始碼閱讀:SDWebImage(十七)——UIView+WebCacheOperation

原始碼閱讀:SDWebImage(十八)——UIView+WebCache

原始碼閱讀:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat

原始碼閱讀:SDWebImage(二十)——UIButton+WebCache

原始碼閱讀:SDWebImage(二十一)——UIImageView+WebCache/UIImageView+HighlightedWebCache

相關文章