從sd_setImageWithURL:方法談SDWebImage (一)

SDWebImage是GitHub上耳熟能詳的iOS網絡圖片下載三方庫。在iOS 中圖片下載使用最多的方法應該是sd_setImageWithURL:,僅僅一個方法就可以開啟異步下載、合理緩存機制。
SDWebImage中最常用的方法如下:

#import "UIImageView+WebCache.h" // 導入UIImageView的分類

UIImageView *avatar = [UIImageView new];
// 調起SDWebImage的下載代碼
[avatar sd_setImageWithURL:[NSURL URLWithString:imageURL]];

上訴代碼就能夠直接從網絡上下載圖片并且顯示。接下來我們從UIImageView+WebCache的sd_setImageWithURL:方法開始一步一步的來了解SDWebImage。
UIImageView+WebCache是SDWebImage為常用的UIKit做的擴展之一。其它的WebCache Categories主要包含

#import "NSImage+WebCache.h" // Mac開發 使用
#import "MKAnnotationView+WebCache.h"
#import "UIButton+WebCache.h"
#import "UIImageView+HighlightedWebCache.h"
#import "UIImageView+WebCache.h"
#import "UIView+WebCache.h"

以UIView+WebCache.h作為基礎的Category封裝了最基礎功能:圖片下載、取消下載。其它的Category根據自身的特點擴展了更多便利的方法但各自實現的方法最終都是調用到UIView+WebCache來進行圖片下載,本文只拿出UIView+WebCache來進行分析,其它Category都是對UIView+WebCache的擴展使用。

UIView+WebCache中的核心方法只有一個:

- (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;

UIView+WebCache.m下載的實現,我去除了一些代碼,保留了重要的下載代碼如下:


- (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 {
    // 取消之前得下載操作
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    // 動態綁定imageURLKey,用來存儲和返回下載圖片的地址
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    if (url) {  // 判斷url是否有效
      // 開啟下載任務,返回下載的operation
        __weak __typeof(self)wself = self;
        // 構建圖片下載的operation(需遵守SDWebImageOperation協議,用于取消operation)
        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;
            dispatch_main_async_safe(^{
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {  
                     // options設置了不自動 調用Set圖片的策略時直接回調結果
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    // 自動設置圖片。(內部會做判斷設置UIImage的圖片、UIButton不同狀態的圖片和背景圖等)
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {
                }
              //  結果的回調,包含圖片、錯誤、緩存類型、及圖片
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        // 由于異步下載圖片,根據validOperationKey存儲圖片下載操作,以備圖片下載未完成之時用來取消operation
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else { 
      // 直接回調失敗
    }
}

需要注意的地方:

dispatch_main_async_safe(); 是作者用來確保在主線程執行代碼的宏定義,之前使用[NSThread mainThread]來判斷,現在版本使用
使用strcmp來判斷dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL),dispatch_queue_get_label(dispatch_get_main_queue() 是否相等

使用關聯對象為UIView增加圖片URL
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, &imageURLKey);

接下來讓我們一行行的看下代碼。
首先,取消之前下載操作

    //  取得一個下載操作key(nil,用類名做key)
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    // 取消下載
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

sd_cancelImageLoadOperationWithKey:方法是在一個新的分類
在UIView+WebCacheOperation中:

UIView+WebCacheOperation.h

- sd_setImageLoadOperation:forKey:
- sd_cancelImageLoadOperationWithKey:
- sd_removeImageLoadOperationWithKey:

在UIView+WebCacheOperation.h的API都是對下載operation的操作。綁定operation、取消operation、移除operation。在UIView+WebCacheOperation.m同樣使用關聯對象針對每個UIKit對象在內存中維護一個字典operationDictionary。可以對不同的key值添加對應的下載operation,也可以在下載操作沒有完成的時候根據key取到operation進行取消。在這里作者將不同的功能方法放在不同的Category中,便于管理區分。

operationDictionary的key一般是類名,如此同一個UIImageView同時調用兩次,第一次的下載操作會先被取消,然后將operationDictionary的中的operation對應到第二次的下載操作。針對特殊情況作者也做了修改(如按鈕不同狀態下的圖片key值是添加了狀態的枚舉值的字符串區分,所以按鈕可以同時開啟多個圖片下載任務)對開發人員一般不需要配置。

- (SDOperationsDictionary *)operationDictionary {
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}
  • 目前我們大概了解了SDWebImage的下載的過程:從sd_setImageWithURL開始—>sd_internalSetImageWithURL:—>SDWebImageManager.sharedManager開啟異步下載同時返回一個下載任務operation,以key:operation形式存放到UIView的關聯對象(operationDictionary字典)中。以后可根據key取出對應的operation進行取消等操作——>圖片異步下載成功or失敗,根據設置的options策略賦值圖片到相應的UI控件上。

  • 每個UIView的控件SDWebimage都用關聯對象的方式在UIView+WebCache中綁定了一個imageURL,在UIView+WebCacheOperation中綁定了用于存放下載圖片operation的字典operationDictionary。


以上,可以看出圖片下載開始前和完成后的代碼邏輯。
而圖片下載關鍵在SDWebImageManager.sharedManager的loadImageWithURL:的方法。下篇文章談談SDWebImageManager又是如何下載圖片!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容