SDWebImage原始碼解析(四)

huang303513發表於2017-05-03

1 概述

這篇博文將分析SDWebImageDownloaderSDWebImageDownloaderOperationSDWebImage通過這兩個類處理圖片的網路載入。SDWebImageManager通過屬性imageDownloader持有SDWebImageDownloader並且呼叫它的downloadImageWithURL來從網路載入圖片。SDWebImageDownloader實現了圖片載入的具體處理,如果圖片在快取存在則從快取區,如果快取不存在,則直接建立一個SDWebImageDownloaderOperation物件來下載圖片。管理NSURLRequest物件請求頭的封裝、快取、cookie的設定。載入選項的處理等功能。管理Operation之間的依賴關係。SDWebImageDownloaderOperation是一個自定義的並行Operation子類。這個類主要實現了圖片下載的具體操作、以及圖片下載完成以後的圖片解壓縮、Operation生命週期管理等。

2 SDWebImageDownloader分析

SDWebImageDownlaoder是一個單列物件,主要做了如下工作:

  • 定義了SDWebImageDownloaderOptions這個列舉屬性,通過這個列舉屬性來設定圖片從網路載入的不同情況。

  • 定義並管理了NSURLSession物件,通過這個物件來做網路請求,並且實現物件的代理方法。

  • 定義一個NSURLRequest物件,並且管理請求頭的拼裝。

  • 對於每一個網路請求,通過一個SDWebImageDownloaderOperation自定義的NSOperation來操作網路下載。

  • 管理網路載入過程和完成時候的回撥工作。通過addProgressCallback實現。

2.1 SDWebImageDownloaderOptions列舉型別

可以通過這個列舉型別來控制網路載入、請求頭、快取策略等。

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    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,
};

2.2 SDWebImageDownloader的屬性和初始化

可以通過它的屬性對最大並行下載數量、超時時間、operation之間的下載順序、做處理。

/**
 * 當圖片下載完成以後,加壓縮圖片以後再換成。這樣可以提升效能但是會佔用更多的儲存空間。
 * 模式YES,如果你因為過多的記憶體消耗導致一個奔潰,可以把這個屬性設定為NO。
 */
@property (assign, nonatomic) BOOL shouldDecompressImages;

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

/**
 當前並行下載數量
 */
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
 下載超時時間設定
 */
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
 改變下載operation的執行順序。預設是FIFO。
 */
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

/**
 單列方法。返回一個單列物件
 @return 返回一個單列的SDWebImageDownloader物件
 */
+ (nonnull instancetype)sharedDownloader;
/**
 為圖片載入request設定一個SSL證書物件。
 */
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;

/**
 Basic認證請求設定使用者名稱和密碼
 */
@property (strong, nonatomic, nullable) NSString *username;
@property (strong, nonatomic, nullable) NSString *password;

/**
 * 為http請求設定header。
 * 每一request執行的時候,這個Block都會被執行。用於向http請求新增請求域。
 */
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
 初始化一個請求物件

 @param sessionConfiguration NSURLSessionTask初始化配置
 @return 返回一個SDWebImageDownloader物件
 */
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
/**
 設定請求頭域

 @param value 請求頭域值
 @param field 請求頭域名
 */
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
/*
*獲取請求頭域的值
*/
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
/**
 設定一個`SDWebImageDownloaderOperation`的子類作為`NSOperation`來構建request來下載一張圖片。

 @param operationClass 指定的子類
 */
- (void)setOperationClass:(nullable Class)operationClass;
/**
 所有的下載圖片的Operation都加入NSoperationQueue中
 */
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;

/**
 最後一個新增的Operation
 */
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;

/**
 自定義的NSOperation子類
 */
@property (assign, nonatomic, nullable) Class operationClass;

/**
 用於記錄url和他對應的SDWebImageDownloaderOperation物件。
 */
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;

/**
 請求頭域字典
 */
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
/**
 通過這個`NSURLSession`建立請求
 */
@property (strong, nonatomic) NSURLSession *session;

2.2 downloadImageWithURL方法

這個方法是SDWebImageDownloader的核心方法。SDWebImageManager通過這個方法來實現圖片從網路載入。

/**
 新建一個SDWebImageDownloadOperation物件來來做具體的下載操作。同時指定快取策略、cookie策略、自定義請求頭域等。

 @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;
        }
        /*
         *為了避免可能存在的NSURLCache和SDImageCache同時快取。我們預設不允許image物件的NSURLCache物件。
         具體快取策略參考http://www.jianshu.com/p/855c2c6e761f
         */
        NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
        if (options & SDWebImageDownloaderUseNSURLCache) {
            if (options & SDWebImageDownloaderIgnoreCachedResponse) {
                cachePolicy = NSURLRequestReturnCacheDataDontLoad;
            } else {
                cachePolicy = NSURLRequestUseProtocolCachePolicy;
            }
        }
        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;
        }
        //把operatin新增進入NSOperationQueue中
        [sself.downloadQueue addOperation:operation];
        /*
         如果是LIFO這種模式,則需要手動指定operation之間的依賴關係
         */
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            //如果是LIFO,則讓前面的operation依賴於最新新增的operation
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }
        return operation;
    }];
}
/**
 給下載過程新增進度

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

如果要取消一個下載操作,使用cancel方法來處理

/**
 移除一個圖片載入操作

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

另外還有NSURLSession的代理方法,這裡就不細講了。如果興趣可以參考AFNetWorking原始碼分析。

3 SDWebImageDownloaderOperation分析

SDWebImageDownloaderOperation是一個自定義、並行的NSOperation子類。這個子類主要實現的功能有:

  • 由於只自定義的並行NSOperation,所以需要管理executing,finished等各種屬性的處理,並且手動觸發KVO。

  • start(NSOperation規定,沒有為什麼)方法裡面實現主要邏輯。

  • NSURLSessionTaskDelegateNSURLSessionDataDelegate中處理資料的載入,以及進度Block的處理。

  • 如果unownedSession屬性因為某種原因是nil,則手動初始化一個做網路請求。

  • 在代理方法中對認證、資料拼裝、完成回撥Block做處理。

  • 通過傳送SDWebImageDownloadStopNotification,SDWebImageDownloadFinishNotification,SDWebImageDownloadReceiveResponseNotification,SDWebImageDownloadStartNotification來通知Operation的狀態。

具體完整原始碼如下:

NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";

static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";

typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;

@interface SDWebImageDownloaderOperation ()

/**
 回撥Block列表
 */
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;

/**
 自定義並行Operation需要管理的兩個屬性。預設是readonly的,我們這裡通過宣告改為可修改的。方便我們在後面操作。
 */
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;

/**
 儲存圖片資料
 */
@property (strong, nonatomic, nullable) NSMutableData *imageData;
/**
 通過SDWebImageDownloader傳過來。所以這裡是weak。因為他是通過SDWebImageDownloader管理的。
 */
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
/**
 如果unownedSession是nil,我們需要手動建立一個並且管理他的生命週期和代理方法
 */
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;

/**
 dataTask物件
 */
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;

/**
 一個並行queue。用於控制資料的處理
 */
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;

#if SD_UIKIT

/**
 如果使用者設定了後臺繼續載入選線。則通過backgroundTask來繼續下載圖片
 */
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif

@end

@implementation SDWebImageDownloaderOperation {
    size_t width, height;
#if SD_UIKIT || SD_WATCH
    UIImageOrientation orientation;
#endif
}

@synthesize executing = _executing;
@synthesize finished = _finished;

- (nonnull instancetype)init {
    return [self initWithRequest:nil inSession:nil options:0];
}

- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                              inSession:(nullable NSURLSession *)session
                                options:(SDWebImageDownloaderOptions)options {
    if ((self = [super init])) {
        _request = [request copy];
        _shouldDecompressImages = YES;
        _options = options;
        _callbackBlocks = [NSMutableArray new];
        //預設情況下。_executing和finished都是NO
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        _unownedSession = session;
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (void)dealloc {
    SDDispatchQueueRelease(_barrierQueue);
}

/**
 給Operation新增進度和回撥Block

 @param progressBlock 進度Block
 @param completedBlock 回撥Block
 @return 回撥字典
 */
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    //把Operation對應的回撥和進度Block存入一個字典中
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    //把完成和進度Block加入callbackBlocks中
    dispatch_barrier_async(self.barrierQueue, ^{
        [self.callbackBlocks addObject:callbacks];
    });
    return callbacks;
}

- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
    __block NSMutableArray<id> *callbacks = nil;
    dispatch_sync(self.barrierQueue, ^{
        // We need to remove [NSNull null] because there might not always be a progress block for each callback
        callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
        [callbacks removeObjectIdenticalTo:[NSNull null]];
    });
    return [callbacks copy];    // strip mutability here
}

- (BOOL)cancel:(nullable id)token {
    __block BOOL shouldCancel = NO;
    dispatch_barrier_sync(self.barrierQueue, ^{
        [self.callbackBlocks removeObjectIdenticalTo:token];
        if (self.callbackBlocks.count == 0) {
            shouldCancel = YES;
        }
    });
    if (shouldCancel) {
        [self cancel];
    }
    return shouldCancel;
}

/**
 並行的Operation需要重寫這個方法.在這個方法裡面做具體的處理
 */
- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

#if SD_UIKIT
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        //如果使用者甚至了Background模式,則設定一個backgroundTask
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                //background結束以後。做清理工作
                __strong __typeof (wself) sself = wself;
                if (sself) {
                    [sself cancel];
                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        NSURLSession *session = self.unownedSession;
        //如果SDWebImageDownloader傳入的session是nil,則自己手動初始化一個。
        if (!self.unownedSession) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  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.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }
    //傳送請求
    [self.dataTask resume];

    if (self.dataTask) {
        //第一次呼叫進度BLOCK
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    } else {
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can`t be initialized"}]];
    }

#if SD_UIKIT
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

/**
 如果要取消一個Operation,就會呼叫這個方法。
 */
- (void)cancel {
    @synchronized (self) {
        [self cancelInternal];
    }
}

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel];

    if (self.dataTask) {
        [self.dataTask cancel];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });

        // As we cancelled the connection, its callback won`t be called and thus won`t
        // maintain the isFinished and isExecuting flags.
        //更新狀態
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }

    [self reset];
}

- (void)done {
    self.finished = YES;
    self.executing = NO;
    [self reset];
}

- (void)reset {
    dispatch_barrier_async(self.barrierQueue, ^{
        [self.callbackBlocks removeAllObjects];
    });
    self.dataTask = nil;
    self.imageData = nil;
    if (self.ownedSession) {
        [self.ownedSession invalidateAndCancel];
        self.ownedSession = nil;
    }
}

/**
 需要手動觸發_finished的KVO。這個是自定義併發`NSOperation`必須實現的。

 @param finished 改變狀態
 */
- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
/**
 需要手動觸發_executing的KVO。這個是自定義併發`NSOperation`必須實現的。
 
 @param executing 改變狀態
 */
- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

/**
 返回YES,表明這個NSOperation物件是併發的

 @return 返回bool值
 */
- (BOOL)isConcurrent {
    return YES;
}

#pragma mark NSURLSessionDataDelegate

/*
 
 */
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    
    //`304 Not Modified` is an exceptional one
    if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
        //期望的總長度
        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
        self.expectedSize = expected;
        //進度回撥Block
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, expected, self.request.URL);
        }
        
        self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
        self.response = response;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
        });
    }
    else {
        NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
        
        //This is the case when server returns `304 Not Modified`. It means that remote image is not changed.
        //In case of 304 we need just cancel the operation and return cached image from the cache.
        /*
         如果返回304表示圖片麼有變化。在這種情況下,我們只需要取消operation並且返回快取的圖片就可以了。
         */
        if (code == 304) {
            [self cancelInternal];
        } else {
            [self.dataTask cancel];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });
        
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];

        [self done];
    }
    //這個表示允許繼續載入
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}
/*
 *會被多次呼叫。獲取圖片資料
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    [self.imageData appendData:data];

    if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
        // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
        // Thanks to the author @Nyx0uf

        // Get the total bytes downloaded
        //獲取已經下載的資料長度
        const NSInteger totalSize = self.imageData.length;

        // Update the data source, we must pass ALL the data, not just the new bytes
        CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
        /*
         *width和height都是0的話表示還麼有獲取到圖片的高度和寬度。我們可以通過資料來獲取圖片的寬度和高度
         *此時表示第一次收到圖片資料
         */
        if (width + height == 0) {
            //獲取圖片資料的屬性
            CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
            if (properties) {
                NSInteger orientationValue = -1;
                //獲取高度值
                CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
                if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
                //獲取寬度值
                val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
                if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
                //獲取圖片的方向值
                val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
                if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
                CFRelease(properties);

                // When we draw to Core Graphics, we lose orientation information,
                // which means the image below born of initWithCGIImage will be
                // oriented incorrectly sometimes. (Unlike the image born of initWithData
                // in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
                orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
#endif
            }
        }
        /*
         * 這個表示已經收到部分圖片資料並且還麼有獲取到所有的圖片資料
         */
        if (width + height > 0 && totalSize < self.expectedSize) {
            // Create the image
            CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);

#if SD_UIKIT || SD_WATCH
            // Workaround for iOS anamorphic image
            if (partialImageRef) {
                const size_t partialHeight = CGImageGetHeight(partialImageRef);
                CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
                CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
                CGColorSpaceRelease(colorSpace);
                if (bmContext) {
                    CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
                    CGImageRelease(partialImageRef);
                    partialImageRef = CGBitmapContextCreateImage(bmContext);
                    CGContextRelease(bmContext);
                }
                else {
                    CGImageRelease(partialImageRef);
                    partialImageRef = nil;
                }
            }
#endif

            if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
                UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
#elif SD_MAC
                UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
                //獲取圖片url對應的key
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                //根據原始圖片資料獲取對應scale下面的圖片
                UIImage *scaledImage = [self scaledImageForKey:key image:image];
                //是否解壓縮圖片
                if (self.shouldDecompressImages) {
                    /*
                     *解壓縮圖片
                     */
                    image = [UIImage decodedImageWithImage:scaledImage];
                }
                else {
                    image = scaledImage;
                }
                CGImageRelease(partialImageRef);
                
                [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
            }
        }

        CFRelease(imageSource);
    }

    for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
        progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    //根據request的選項。決定是否快取NSCachedURLResponse
    NSCachedURLResponse *cachedResponse = proposedResponse;

    if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
        // Prevents caching of responses
        cachedResponse = nil;
    }
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

#pragma mark NSURLSessionTaskDelegate
/*
 網路請求載入完成,在這裡處理獲得的資料
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    @synchronized(self) {
        self.dataTask = nil;
        //傳送圖片下載完成的通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
            if (!error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
            }
        });
    }
    if (error) {
        [self callCompletionBlocksWithError:error];
    } else {
        if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
            if (self.imageData) {
                UIImage *image = [UIImage sd_imageWithData:self.imageData];
                //獲取url對應的快取Key
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                
                image = [self scaledImageForKey:key image:image];
                
                // Do not force decoding animated GIFs
                if (!image.images) {
                    //是否加壓縮圖片資料
                    if (self.shouldDecompressImages) {
                        //如果設定了SDWebImageDownloaderScaleDownLargeImages。則返回處理過的圖片
                        if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
#if SD_UIKIT || SD_WATCH
                            image = [UIImage decodedAndScaledDownImageWithImage:image];
                            [self.imageData setData:UIImagePNGRepresentation(image)];
#endif
                        } else {
                            image = [UIImage decodedImageWithImage:image];
                        }
                    }
                }
                //構建回撥Block
                if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                    [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                } else {
                    [self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
                }
            } else {
                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
            }
        }
    }
    [self done];
}

/*
 驗證HTTPS的證書
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    //使用可信任證書機構的證書
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        //如果SDWebImageDownloaderAllowInvalidSSLCertificates屬性設定了,則不驗證SSL證書。直接信任
        if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        } else {
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            disposition = NSURLSessionAuthChallengeUseCredential;
        }
    } else {
        //使用自己生成的證書
        if (challenge.previousFailureCount == 0) {
            if (self.credential) {
                credential = self.credential;
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    //驗證證書
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
#pragma mark Helper methods

#if SD_UIKIT || SD_WATCH

/**
 把整數轉換為對應的列舉值

 @param value 整數值
 @return 列舉值
 */
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
    switch (value) {
        case 1:
            return UIImageOrientationUp;
        case 3:
            return UIImageOrientationDown;
        case 8:
            return UIImageOrientationLeft;
        case 6:
            return UIImageOrientationRight;
        case 2:
            return UIImageOrientationUpMirrored;
        case 4:
            return UIImageOrientationDownMirrored;
        case 5:
            return UIImageOrientationLeftMirrored;
        case 7:
            return UIImageOrientationRightMirrored;
        default:
            return UIImageOrientationUp;
    }
}
#endif

/**
* 通過image物件獲取對應scale模式下的影像
 */
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
    return SDScaledImageForKey(key, image);
}

- (BOOL)shouldContinueWhenAppEntersBackground {
    return self.options & SDWebImageDownloaderContinueInBackground;
}

- (void)callCompletionBlocksWithError:(nullable NSError *)error {
    [self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
/**
 處理回撥

 @param image UIImage資料
 @param imageData Image的data資料
 @param error 錯誤
 @param finished 是否完成的標記位
 */
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
                            imageData:(nullable NSData *)imageData
                                error:(nullable NSError *)error
                             finished:(BOOL)finished {
    //獲取key對應的回撥Block陣列
    NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
    dispatch_main_async_safe(^{
        //呼叫回撥
        for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
            completedBlock(image, imageData, error, finished);
        }
    });
}
@end

最後原文地址.html),demo地址

相關文章