SDWebImage源碼解讀之SDWebImageDownloader

第八篇

前言

SDWebImageDownloader這個(gè)類非常簡(jiǎn)單朴摊,作者的設(shè)計(jì)思路也很清晰句占,但是我想在這說點(diǎn)題外話岛杀。

如果有人問你:你怎么看待編程這件事诱篷?你怎么回答壶唤。這個(gè)問題是我在看這個(gè)類的時(shí)候,忽然出現(xiàn)在我腦子中的棕所。我突然意識(shí)到闸盔,其實(shí)不管是函數(shù)還是屬性,他們都是數(shù)據(jù)橙凳。我們編寫的所有程序都是在處理數(shù)據(jù)蕾殴。函數(shù)本身也是一種特殊的數(shù)據(jù)

真正難的是生產(chǎn)數(shù)據(jù)的這一過程岛啸。舉個(gè)例子钓觉,給你一堆菜籽,要求生產(chǎn)出油來(lái)坚踩。怎么辦荡灾?我們首先為這個(gè)任務(wù)設(shè)計(jì)一個(gè)函數(shù):

- (油)用菜籽生產(chǎn)油(菜籽);

這就是我們最外層的函數(shù)瞬铸,也應(yīng)該是我們最開始想到的函數(shù)批幌。然后經(jīng)過我們的研究發(fā)現(xiàn),這個(gè)生產(chǎn)過程很復(fù)雜嗓节,必須分工合作才能實(shí)現(xiàn)荧缘。于是我們把這個(gè)任務(wù)分割為好幾個(gè)小任務(wù):

1. - (干凈的菜籽)取出雜質(zhì)(菜籽);
2. - (炒熟的菜籽)把菜籽炒一下(干凈的菜籽);
3. - (蒸了的菜籽)把菜籽蒸一下(炒熟的菜籽);
4. - (捆好的菜籽)把菜籽包捆成一塊(蒸了的菜籽);
5. - (油)撞擊菜籽包(捆好的菜籽);

大家有沒有發(fā)現(xiàn),整個(gè)榨油的過程就是對(duì)數(shù)據(jù)的處理拦宣。這一點(diǎn)其實(shí)很重要截粗。如果沒有把- (油)用菜籽生產(chǎn)油(菜籽);這一任務(wù)進(jìn)行拆分鸵隧,我們就會(huì)寫出復(fù)雜無(wú)比的函數(shù)绸罗。那么就有人要問了,只要實(shí)現(xiàn)這個(gè)功能就行了唄豆瘫。其實(shí)這往往是寫不出好代碼的原因珊蟀。

整個(gè)任務(wù)的設(shè)計(jì)應(yīng)該是事先就設(shè)計(jì)好的。任務(wù)被分割成更小更簡(jiǎn)單的部分外驱,然后再去實(shí)現(xiàn)這些最小的任務(wù)育灸,不應(yīng)該是編寫邊分割任務(wù),往往臨時(shí)分割的任務(wù)(也算是私有函數(shù)吧)沒有最正確的界限昵宇。

有了上邊合理的分工之后呢描扯,我們就可以進(jìn)行任務(wù)安排了。我們回到現(xiàn)實(shí)開發(fā)中來(lái)趟薄。上邊5個(gè)子任務(wù)的難度是不同的。有的人可能基礎(chǔ)比較差典徊,那么讓他去干篩菜籽這種體力活杭煎,應(yīng)該沒問題恩够。那些炒或者蒸的子任務(wù)是要掌握火候的,也就是說有點(diǎn)技術(shù)含量羡铲。那么就交給能勝任這項(xiàng)工作的人去做蜂桶。所有的這一切,我們只要事先定義好各自的生產(chǎn)結(jié)果就行了也切,完全不影響每個(gè)程序的執(zhí)行扑媚。

怎么樣?大家體會(huì)到這種編程設(shè)計(jì)的好處了嗎雷恃?我還可以進(jìn)行合并疆股,把炒和煮合成一個(gè)小組,完全可行啊倒槐。好了這方面的思考就說這么多吧旬痹。如果我想買煮熟了的菜籽,是不是也很簡(jiǎn)單讨越?

有的人用原始的撞擊菜籽包榨油两残,有的人卻用最先進(jìn)的儀器榨油,這就是編程技術(shù)和知識(shí)深度的區(qū)別啊把跨。

SDWebImageDownloaderOptions

言歸正傳人弓,當(dāng)我們需要給某個(gè)功能添加Options的時(shí)候,一般使用枚舉來(lái)實(shí)現(xiàn)着逐。我們先看看支持的選項(xiàng):

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    SDWebImageDownloaderProgressiveDownload = 1 << 1,  // 帶有進(jìn)度
    SDWebImageDownloaderUseNSURLCache = 1 << 2,  // 使用URLCache
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,  // 不緩存響應(yīng)
    SDWebImageDownloaderContinueInBackground = 1 << 4,  // 支持后臺(tái)下載
    SDWebImageDownloaderHandleCookies = 1 << 5,   // 使用Cookies
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,  // 允許驗(yàn)證SSL
    SDWebImageDownloaderHighPriority = 1 << 7,      // 高權(quán)限
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,  // 裁剪大圖片
};

這里提供了這么幾種不同的選項(xiàng)崔赌,大家可以根據(jù)自己的需求選個(gè)合適的選項(xiàng)。這里作者使用了掩碼滨嘱。比如說峰鄙,1 << 1 ,表示把1左移一位,我們把1攜程二進(jìn)制為:00000001太雨,那么左移一位后就是:00000010 轉(zhuǎn)成10進(jìn)制后就是2吟榴,也就是說左移一位表示在原來(lái)的值上乘以2。

再舉個(gè)例子囊扳,當(dāng)判斷self.option是否是SDWebImageDownloaderIgnoreCachedResponse選項(xiàng)時(shí)吩翻,應(yīng)該這么判斷:

self.option & SDWebImageDownloaderIgnoreCachedResponse

SDWebImageDownloaderExecutionOrder

SDWebImageDownloaderExecutionOrder定義了數(shù)據(jù)被調(diào)用的順序。按照一般的想法锥咸。下載應(yīng)該按照數(shù)據(jù)放入隊(duì)列的順序依次進(jìn)行狭瞎,但也支持后進(jìn)先出這種方式。

一個(gè)下載管理器應(yīng)該這樣管理下載搏予,肯定有一個(gè)下載列表熊锭,我們可以假定這個(gè)列表保存在一個(gè)數(shù)組中,正常情況下我們應(yīng)該每次取出數(shù)組中第1個(gè)元素來(lái)下載,這就是FIFO(先進(jìn)先出)碗殷。那么我們要改為L(zhǎng)IFO(后進(jìn)先出)精绎,應(yīng)該是針對(duì)某一個(gè)下載的,不應(yīng)該是把取出數(shù)據(jù)的順序改為從數(shù)組的最后一個(gè)元素取出锌妻。

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

但是我有一個(gè)疑問代乃,如果我只是想暫停某一個(gè)下載,我該怎么辦呢仿粹?如果直接取消搁吓,那么點(diǎn)擊繼續(xù)的時(shí)候,要重新開啟下載吭历,肯定影響體驗(yàn)堕仔。這一點(diǎn)跟使用數(shù)組管理任務(wù)有點(diǎn)不同。

輔助部分

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

通過extern,我們就能夠使用其他文件的代碼毒涧。

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

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

命名Block

typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary;
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;

命名字典

typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);

這個(gè)block允許我們自定義請(qǐng)求頭贮预,通過Block傳值,有一定的優(yōu)點(diǎn)契讲,我們可以拿到一些參數(shù)仿吞,然后在加工成我們需要的數(shù)據(jù),最后返回捡偏。

SDWebImageDownloadToken

SDWebImageDownloadToken作為每一個(gè)下載的唯一身份標(biāo)識(shí)唤冈,SDWebImageDownloader和我們平時(shí)開發(fā)中的下載還是又不一樣的地方的,它弱化了下載過程银伟,比較強(qiáng)調(diào)的是下載結(jié)果你虹。不支持?jǐn)帱c(diǎn)下載(當(dāng)然這可能沒有必要)。

如果我們需要設(shè)計(jì)一個(gè)自己的下載管理者彤避,我們就應(yīng)該設(shè)計(jì)一個(gè)類似SDWebImageDownloadToken這樣的下載對(duì)象封裝類傅物,我需要的所有信息,都能在這個(gè)對(duì)象中獲取琉预,大家如果有興趣董饰,可以看看MCDownloadManager.

/**
 *  A token associated with each download. Can be used to cancel a download
 */
@interface SDWebImageDownloadToken : NSObject

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

@end

SDWebImageDownloader.h

/**
 * Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
 * Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
 */
@property (assign, nonatomic) BOOL shouldDecompressImages;

/**
 *  The maximum number of concurrent downloads
 */
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;

/**
 * Shows the current amount of downloads that still need to be downloaded
 */
@property (readonly, nonatomic) NSUInteger currentDownloadCount;


/**
 *  The timeout value (in seconds) for the download operation. Default: 15.0.
 */
@property (assign, nonatomic) NSTimeInterval downloadTimeout;


/**
 * Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
 */
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

/**
 *  Singleton method, returns the shared instance
 *
 *  @return global shared instance of downloader class
 */
+ (nonnull instancetype)sharedDownloader;

/**
 *  Set the default URL credential to be set for request operations.
 */
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;

/**
 * Set username
 */
@property (strong, nonatomic, nullable) NSString *username;

/**
 * Set password
 */
@property (strong, nonatomic, nullable) NSString *password;

/**
 * Set filter to pick headers for downloading image HTTP request.
 *
 * This block will be invoked for each downloading image request, returned
 * NSDictionary will be used as headers in corresponding HTTP request.
 */
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;

初始化方法

/**
 * Creates an instance of a downloader with specified session configuration.
 * *Note*: `timeoutIntervalForRequest` is going to be overwritten.
 * @return new instance of downloader class
 */
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;

我們都知道: 一個(gè)NSURLSession會(huì)話,使用NSURLSessionConfiguration進(jìn)行配置圆米,利用這一點(diǎn)卒暂,我們?cè)谠O(shè)計(jì)自己的網(wǎng)絡(luò)框架的時(shí)候,可以參考NSURLSessionConfiguration娄帖。暴露少量的屬性來(lái)配置網(wǎng)絡(luò)請(qǐng)求也祠,后期維護(hù)起來(lái)也比較容易。

使用NS_DESIGNATED_INITIALIZER強(qiáng)調(diào)該方法是建議的初始化方法近速。

其他的方法

/**
 * Set a value for a HTTP header to be appended to each download HTTP request.
 *
 * @param value The value for the header field. Use `nil` value to remove the header.
 * @param field The name of the header field to set.
 */
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;

/**
 * Returns the value of the specified HTTP header field.
 *
 * @return The value associated with the header field field, or `nil` if there is no corresponding header field.
 */
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;

/**
 * Sets a subclass of `SDWebImageDownloaderOperation` as the default
 * `NSOperation` to be used each time SDWebImage constructs a request
 * operation to download an image.
 *
 * @param operationClass The subclass of `SDWebImageDownloaderOperation` to set 
 *        as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`.
 */
- (void)setOperationClass:(nullable Class)operationClass;

/**
 * Creates a SDWebImageDownloader async downloader instance with a given URL
 *
 * The delegate will be informed when the image is finish downloaded or an error has happen.
 *
 * @see SDWebImageDownloaderDelegate
 *
 * @param url            The URL to the image to download
 * @param options        The options to be used for this download
 * @param progressBlock  A block called repeatedly while the image is downloading
 *                       @note the progress block is executed on a background queue
 * @param completedBlock A block called once the download is completed.
 *                       If the download succeeded, the image parameter is set, in case of error,
 *                       error parameter is set with the error. The last parameter is always YES
 *                       if SDWebImageDownloaderProgressiveDownload isn't use. With the
 *                       SDWebImageDownloaderProgressiveDownload option, this block is called
 *                       repeatedly with the partial image object and the finished argument set to NO
 *                       before to be called a last time with the full image and finished argument
 *                       set to YES. In case of error, the finished argument is always YES.
 *
 * @return A token (SDWebImageDownloadToken) that can be passed to -cancel: to cancel this operation
 */
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

/**
 * Cancels a download that was previously queued using -downloadImageWithURL:options:progress:completed:
 *
 * @param token The token received from -downloadImageWithURL:options:progress:completed: that should be canceled.
 */
- (void)cancel:(nullable SDWebImageDownloadToken *)token;

/**
 * Sets the download queue suspension state
 */
- (void)setSuspended:(BOOL)suspended;

/**
 * Cancels all download operations in the queue
 */
- (void)cancelAllDownloads;

SDWebImageDownloader.m

@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
@property (assign, nonatomic, nullable) Class operationClass;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@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;

這些屬性可以說都是為了完成管理下載任務(wù)而存在的诈嘿。

  • downloadQueue 隊(duì)列
  • lastAddedOperation 用于記錄最后添加的操作
  • operationClass 支持我們自定義的操作類
  • URLOperations 存放著所有的operation
  • HTTPHeaders HTTP請(qǐng)求頭
  • barrierQueue
  • session

initialize

initializeload 這兩個(gè)方法比較特殊堪旧,我們通過下邊這個(gè)表格來(lái)看看他們的區(qū)別

.. +(void)load +(void)initialize
執(zhí)行時(shí)機(jī) 在程序運(yùn)行后立即執(zhí)行 在類的方法第一次被調(diào)時(shí)執(zhí)行
若自身未定義,是否沿用父類的方法永淌?
類別中的定義 全都執(zhí)行崎场,但后于類中的方法 覆蓋類中的方法,只執(zhí)行一個(gè)
 + (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];
    }
}

上邊的方法是為了給圖片下載綁定一個(gè)SDNetworkActivityIndicator,只有當(dāng)這個(gè)SDNetworkActivityIndicator文件存在的情況下才會(huì)執(zhí)行遂蛀,目的就是當(dāng)下在圖片是,狀態(tài)欄會(huì)轉(zhuǎn)小菊花干厚。

初始化實(shí)現(xiàn)

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

這里邊做了必要的初始化李滴,默認(rèn)最大支持的并發(fā)數(shù)為6個(gè),也就是說可以同時(shí)下載6張圖片蛮瞄。**我們看看image/webp,image/*;q=0.8是什么意思所坯,image/webp是web格式的圖片,q=0.8指的是權(quán)重系數(shù)為0.8挂捅,q的取值范圍是0 - 1芹助, 默認(rèn)值為1,q作用于它前邊分號(hào);前邊的內(nèi)容闲先。在這里状土,image/webp,image/*;q=0.8表示優(yōu)先接受image/webp,其次接受image/*的圖片。

set或者get方法

- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
    if (value) {
        self.HTTPHeaders[field] = value;
    }
    else {
        [self.HTTPHeaders removeObjectForKey:field];
    }
}

- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
    return self.HTTPHeaders[field];
}

- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}

- (NSUInteger)currentDownloadCount {
    return _downloadQueue.operationCount;
}

- (NSInteger)maxConcurrentDownloads {
    return _downloadQueue.maxConcurrentOperationCount;
}

- (void)setOperationClass:(nullable Class)operationClass {
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
        _operationClass = operationClass;
    } else {
        _operationClass = [SDWebImageDownloaderOperation class];
    }
}

為每個(gè)URL綁定事件

- (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.
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    dispatch_barrier_sync(self.barrierQueue, ^{
        SDWebImageDownloaderOperation *operation = self.URLOperations[url];
        if (!operation) {
            operation = createCallback();
            self.URLOperations[url] = operation;

            __weak SDWebImageDownloaderOperation *woperation = 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;
}

每一個(gè)URL都會(huì)被作為每個(gè)下載的唯一標(biāo)識(shí)伺糠,每一個(gè)下載都會(huì)綁定一個(gè)progressBlock和completeBlock蒙谓,最終我們還要使用這個(gè)URL跟NSOperation建立聯(lián)系。

關(guān)于如何創(chuàng)建SDWebImageDownloaderOperation

這一段代碼训桶,我采用注釋的方式累驮,大家看注釋就行了。

- (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;
        
        // 1.設(shè)置超時(shí)時(shí)間
        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
        // 2.創(chuàng)建request舵揭,注意我們?cè)O(shè)置的緩存策略的選擇
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        
        // 3.設(shè)置請(qǐng)求頭部
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }
        
        // 4.創(chuàng)建操作對(duì)象
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        
        // 5.給操作對(duì)象設(shè)置urlCredential
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        // 6.設(shè)置操作級(jí)別
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }

        // 7.把操作添加到隊(duì)列
        [sself.downloadQueue addOperation:operation];
        
        // 8.根據(jù)executionOrder設(shè)置谤专,設(shè)置依賴
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

取消摸個(gè)操作

- (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];
        }
    });
}

全部暫停或取消

- (void)setSuspended:(BOOL)suspended {
    (self.downloadQueue).suspended = suspended;
}

- (void)cancelAllDownloads {
    [self.downloadQueue cancelAllOperations];
}

會(huì)話的代理

我們?cè)诔跏蓟椒ㄖ袆?chuàng)建了會(huì)話:

self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                     delegate:self
                                                delegateQueue:nil];

delegate設(shè)置為自己午绳,也就是當(dāng)使用這個(gè)會(huì)話請(qǐng)求數(shù)據(jù)置侍,收到響應(yīng)時(shí),會(huì)調(diào)用SDWebImageDownloader.m中的代理方法箱叁,然后再調(diào)用SDWebImageDownloaderOperation中的代理方法處理事情墅垮。

那么作者為什么這么設(shè)計(jì)呢?目的就是為了共用一個(gè)URLSession耕漱。

- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
    SDWebImageDownloaderOperation *returnOperation = nil;
    for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
        if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
            returnOperation = operation;
            break;
        }
    }
    return returnOperation;
}

#pragma mark NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

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

    [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {

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

    [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {

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

    [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}

#pragma mark 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];
}

由于個(gè)人知識(shí)有限算色,如有錯(cuò)誤之處,還望各路大俠給予指出啊

  1. SDWebImage源碼解讀 之 NSData+ImageContentType 簡(jiǎn)書 博客園
  2. SDWebImage源碼解讀 之 UIImage+GIF 簡(jiǎn)書 博客園
  3. SDWebImage源碼解讀 之 SDWebImageCompat 簡(jiǎn)書 博客園
  4. SDWebImage源碼解讀 之SDWebImageDecoder 簡(jiǎn)書 博客園
  5. SDWebImage源碼解讀 之SDWebImageCache(上) 簡(jiǎn)書 博客園
  6. SDWebImage源碼解讀之SDWebImageCache(下) 簡(jiǎn)書 博客園
  7. SDWebImage源碼解讀之SDWebImageDownloaderOperation 簡(jiǎn)書 博客園
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末螟够,一起剝皮案震驚了整個(gè)濱河市灾梦,隨后出現(xiàn)的幾起案子峡钓,更是在濱河造成了極大的恐慌,老刑警劉巖若河,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件能岩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡萧福,警方通過查閱死者的電腦和手機(jī)拉鹃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲫忍,“玉大人膏燕,你說我怎么就攤上這事∥蛎瘢” “怎么了坝辫?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)射亏。 經(jīng)常有香客問我近忙,道長(zhǎng),這世上最難降的妖魔是什么智润? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任及舍,我火速辦了婚禮,結(jié)果婚禮上做鹰,老公的妹妹穿的比我還像新娘击纬。我一直安慰自己,他們只是感情好钾麸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布更振。 她就那樣靜靜地躺著,像睡著了一般饭尝。 火紅的嫁衣襯著肌膚如雪肯腕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天钥平,我揣著相機(jī)與錄音实撒,去河邊找鬼。 笑死涉瘾,一個(gè)胖子當(dāng)著我的面吹牛知态,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播立叛,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼负敏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了秘蛇?” 一聲冷哼從身側(cè)響起其做,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顶考,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后妖泄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驹沿,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蹈胡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了渊季。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡审残,死狀恐怖梭域,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搅轿,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布富玷,位于F島的核電站璧坟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赎懦。R本人自食惡果不足惜雀鹃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望励两。 院中可真熱鬧黎茎,春花似錦、人聲如沸当悔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盲憎。三九已至嗅骄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饼疙,已是汗流浹背溺森。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窑眯,地道東北人屏积。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像磅甩,于是被迫代替她去往敵國(guó)和親炊林。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容