SDWebImage之SDWebImageDownloader


SDWebImageDownloader完成了對網絡圖片的異步下載工作,准確說這個類是一個文件下載的工具類,真正的網絡請求是在繼承於NSOperationSDWebImageDownloaderOperation類實現的。SDWebImageDownloader的主要任務是下載相關配置項的管理,包括下載隊列的先后順序、最大下載任務數量控制、下載隊列中的任務創建、取消、暫停等任務管理,以及其他 HTTPS 和 HTTP Header的設置。

1.SDWebImageDownloaderOptions

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    //默認模式
    SDWebImageDownloaderLowPriority = 1 << 0,
    //本模式在返回進度Block的同時,同事返回completedBlock,里面的UIImage就是當前下載時的圖片,可以實現將圖片一點點顯示出來的功能
    SDWebImageDownloaderProgressiveDownload = 1 << 1,
    //默認情況下,http請求阻止使用NSURLCache對象。如果設置了這個標記,則NSURLCache會被http請求使用。
    SDWebImageDownloaderUseNSURLCache = 1 << 2,
    //如果image/imageData是從NSURLCache返回的,則completion這個回調會返回nil
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    //如果app進入后台模式,是否繼續下載,這個是通過在后台申請時間來完成這個操作。如果指定的時間范圍內沒有完成,則直接取消下載。
    SDWebImageDownloaderContinueInBackground = 1 << 4,
    //處理緩存在`NSHTTPCookieStore`對象里面的cookie,通過設置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`來實現的。
    SDWebImageDownloaderHandleCookies = 1 << 5,
    //允許非信任的SSL證書請求。在測試的時候很有用,但是正式環境要小心使用。
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
    //默認情況下,圖片加載的順序是根據加入隊列的順序加載的。但是這個標記會把任務加入隊列的最前面。
    SDWebImageDownloaderHighPriority = 1 << 7,
    //默認情況下,圖片會按照它的原始大小來解碼顯示。這個屬性會根據設備的內存限制調整圖片的尺寸到合適的大小。如果`SDWebImageProgressiveDownload`標記被設置了,則這個flag不起作用。
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};

在這里留意一下,選項使用的是掩碼的方式。例如“1 << 1”,表示把1左移一位。我們把1用二進制表示為:00000001,那么左移一位后就是:00000010 轉成10進制后就是2,也就是說左移一位表示在原來的值上乘以2。

這樣,在使用的時候,當判斷options是否是SDWebImageDownloaderIgnoreCachedResponse選項時,應該這樣來判斷:

self.option & SDWebImageDownloaderIgnoreCachedResponse

2.SDWebImageDownloaderExecutionOrder

typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    /**
     * Default value. All download operations will execute in queue style (first-in-first-out).
     */
    SDWebImageDownloaderFIFOExecutionOrder,

    /**
     * All download operations will execute in stack style (last-in-first-out).
     */
    SDWebImageDownloaderLIFOExecutionOrder
};

SDWebImageDownloaderExecutionOrder定義了數據被調用的順序。按照一般的想法,下載應該按照數據放入隊列的順序依次進行,在SDWebImage中也支持后進先出這種方式。

3.變量定義

extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;

typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);

typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);

typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary;
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;
//自定義請求頭,通過Block傳值,可以拿到一些參數,然后加工成我們需要的數據,最后返回
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);

4.SDWebImageDownloadToken

@interface SDWebImageDownloadToken : NSObject

@property (nonatomic, strong, nullable) NSURL *url;
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;

@end

SDWebImageDownloadToken為每一個下載任務的唯一身份標識SDWebImageDownloader和我們平時開發中的下載有一些不同,它弱化了下載過程,比較強調的是下載結果,不支持斷點下載。

5.相關屬性

//.h文件
@property (assign, nonatomic) BOOL shouldDecompressImages;  //!<當圖片下載完成以后,解碼圖片。如果因為過多的內存消耗導致一個奔潰,可以把這個屬性設置為NO。

@property (assign, nonatomic) NSInteger maxConcurrentDownloads;  //!<最大並行下載的數量

@property (readonly, nonatomic) NSUInteger currentDownloadCount;  //!<當前並行下載數量

@property (assign, nonatomic) NSTimeInterval downloadTimeout;  //!<下載超時時間設置

@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;  //!<改變下載operation的執行順序,默認是FIFO。

@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;  //!<為圖片加載request設置一個SSL證書對象

@property (strong, nonatomic, nullable) NSString *username;  //!<Basic認證請求設置用戶名

@property (strong, nonatomic, nullable) NSString *password;  //!<Basic認證請求設置密碼

@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;  //!<為http請求設置header。每一request執行的時候,這個Block都會被執行。用於向http請求添加請求域

//.m文件
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;  //!<所有的下載圖片的Operation都加入NSOperationQueue中
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;  //!<最后一個添加的Operation
@property (assign, nonatomic, nullable) Class operationClass;  //!<自定義的NSOperation子類
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;  //!<用於記錄url和它對應的SDWebImageDownloaderOperation對象
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;  //!<請求頭域字典
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;

// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;  //!<通過這個`NSURLSession`創建請求

6.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")) {

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop

        // Remove observer in case it was previously added.
        [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                 selector:NSSelectorFromString(@"startActivity")
                                                     name:SDWebImageDownloadStartNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                 selector:NSSelectorFromString(@"stopActivity")
                                                     name:SDWebImageDownloadStopNotification object:nil];
    }
}

該方法是為了給圖片下載綁定一個SDNetworkActivityIndicator,只有當這個SDNetworkActivityIndicator文件存在的情況下才會執行,目的就是當下載圖片時,狀態欄會轉小菊花。

initialize 和 load 這兩個方法比較特殊,我們通過下表來看看它們之間的區別:

  +(void)load +(void)initialize
執行時機 在程序運行后立即執行(在main函數執行之前) 在類的方法第一次被調時執行
若自身未定義,是否沿用父類的方法?
分類中的定義 全都執行,但后於類中的方法 覆蓋類中的方法,只執行一個

7.初始化

/**
 單例方法

 @return 返回SDWebImageDownloader對象
 */
+ (nonnull instancetype)sharedDownloader {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (nonnull instancetype)init {
    return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
}

/**
 初始化一個請求對象

 @param sessionConfiguration NSURLSessionTask初始化配置
 @return 返回SDWebImageDownloader對象
 */
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES;
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;

        sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;

        /**
         *  Create the session for this task
         *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
         *  method calls and completion handler calls.
         */
        self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                     delegate:self
                                                delegateQueue:nil];
    }
    return self;
}

在上面的初始化方法中,我們可以看到,默認最大支持的並發數為6個,也就是說可以同時下載6張圖片。

我們看看image/webp,image/*;q=0.8是什么意思,image/webp是webp格式的圖片,q=0.8指的是權重系數為0.8,q的取值范圍是0 - 1, 默認值為1,q作用於它前邊分號;前邊的內容。在這里,image/webp,image/*;q=0.8表示優先接受image/webp,其次接受image/*的圖片。

8.Set&&Get

/**
 設置請求頭域

 @param value 請求頭域值
 @param field 請求頭域名
 */
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
    if (value) {
        self.HTTPHeaders[field] = value;
    }
    else {
        [self.HTTPHeaders removeObjectForKey:field];
    }
}

/**
 獲取請求頭域的值

 @param field 請求頭域名
 @return 請求頭域值
 */
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
    return self.HTTPHeaders[field];
}

/**
 設置最大並發數

 @param maxConcurrentDownloads 最大並發數
 */
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}

/**
 獲取當前並行下載數量

 @return 當前並行下載數量
 */
- (NSUInteger)currentDownloadCount {
    return _downloadQueue.operationCount;
}

/**
 獲取最大並發數

 @return 最大並發數
 */
- (NSInteger)maxConcurrentDownloads {
    return _downloadQueue.maxConcurrentOperationCount;
}

/**
 設置一個`SDWebImageDownloaderOperation`的子類作為`NSOperation`來構建request來下載一張圖片

 @param operationClass 指定的子類
 */
- (void)setOperationClass:(nullable Class)operationClass {
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
        _operationClass = operationClass;
    } else {
        _operationClass = [SDWebImageDownloaderOperation class];
    }
}

8.下載 

/**
 下載圖片

 @param url url
 @param options 加載選項
 @param progressBlock 進度progress
 @param completedBlock 完成回調
 @return 返回一個SDWebImageDownloadToken,用於關聯一個請求
 */
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;

    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        __strong __typeof (wself) sself = wself;
        //設置超時時間
        NSTimeInterval timeoutInterval = sself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
        //為了避免可能存在的NSURLCache和SDImageCache同時緩存,我們默認不允許image對象的NSURLCache對象。具體緩存策略參考http://www.jianshu.com/p/855c2c6e761f
        NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
        if (options & SDWebImageDownloaderUseNSURLCache) {
            if (options & SDWebImageDownloaderIgnoreCachedResponse) {
                cachePolicy = NSURLRequestReturnCacheDataDontLoad;
            } else {
                cachePolicy = NSURLRequestUseProtocolCachePolicy;
            }
        }
        //創建request
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval]; //使用cookies
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        //使用管道
        request.HTTPShouldUsePipelining = YES;
        //添加自定義請求頭
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }
        //初始化一個自定義NSOperation對象
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options]; //是否解碼返回的圖片
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        //指定驗證信息
        if (sself.urlCredential) {
            //SSL驗證
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {  //Basic驗證
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        //指定優先級
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        //把operation添加進入NSOperationQueue中,當operation添加到downloadQueue,會觸發相應的start方法,開始下載。
 [sself.downloadQueue addOperation:operation]; //如果是LIFO這種模式,則需要手動指定operation之間的依賴關系
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            //如果是LIFO,則讓前面的operation依賴於最新添加的operation
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

/**
 移除一個圖片的下載操作

 @param token 通過token來確定操作
 */
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
    dispatch_barrier_async(self.barrierQueue, ^{
        SDWebImageDownloaderOperation *operation = self.URLOperations[token.url];
        BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
        if (canceled) {
            [self.URLOperations removeObjectForKey:token.url];
        }
    });
}

/**
 給下載過程添加進度

 @param progressBlock 進度Block
 @param completedBlock 完成Block
 @param url url地址
 @param createCallback nil
 @return 返回SDWebImageDownloadToken,方便后面取消
 */
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                   forURL:(nullable NSURL *)url
                                           createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    //如果URL為空,則執行completedBlock回調,並直接返回
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    dispatch_barrier_sync(self.barrierQueue, ^{
        //看是否當前url是否有對應的Operation圖片加載對象
        SDWebImageDownloaderOperation *operation = self.URLOperations[url];
        //如果沒有,則直接創建一個
        if (!operation) {
            //創建一個operation,並且添加到URLOperation中
            operation = createCallback();
            self.URLOperations[url] = operation;

            __weak SDWebImageDownloaderOperation *woperation = operation;
            //設置operation操作完成以后的回調
            operation.completionBlock = ^{
              SDWebImageDownloaderOperation *soperation = woperation;
              if (!soperation) return;
              if (self.URLOperations[url] == soperation) {
                  [self.URLOperations removeObjectForKey:url];
              };
            };
        }
        id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}

/**
 全部暫停/開始

 @param suspended YES/NO
 */
- (void)setSuspended:(BOOL)suspended {
    (self.downloadQueue).suspended = suspended;
}

/**
 全部取消下載
 */
- (void)cancelAllDownloads {
    [self.downloadQueue cancelAllOperations];
}

9.NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];

    [dataOperation URLSession:session task:task didCompleteWithError:error];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    
    completionHandler(request);
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

    // Identify the operation that runs this task and pass it the delegate method
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];

    [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}

當收到數據的時候,會觸發這些代理方法,最后調用SDWebImageDownloaderOperation中的代理方法,來實際處理事情。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM