SDWebImage源碼解析(四)

1 概述

這篇博文將分析SDWebImageDownloaderSDWebImageDownloaderOperationSDWebImage經過這兩個類處理圖片的網絡加載。SDWebImageManager經過屬性imageDownloader持有SDWebImageDownloader而且調用它的downloadImageWithURL來從網絡加載圖片。SDWebImageDownloader實現了圖片加載的具體處理,若是圖片在緩存存在則從緩存區,若是緩存不存在,則直接建立一個SDWebImageDownloaderOperation對象來下載圖片。管理NSURLRequest對象請求頭的封裝、緩存、cookie的設置。加載選項的處理等功能。管理Operation之間的依賴關係。SDWebImageDownloaderOperation是一個自定義的並行Operation子類。這個類主要實現了圖片下載的具體操做、以及圖片下載完成之後的圖片解壓縮、Operation生命週期管理等。html

2 SDWebImageDownloader分析

SDWebImageDownlaoder是一個單列對象,主要作了以下工做:git

  • 定義了SDWebImageDownloaderOptions這個枚舉屬性,經過這個枚舉屬性來設置圖片從網絡加載的不一樣狀況。github

  • 定義並管理了NSURLSession對象,經過這個對象來作網絡請求,而且實現對象的代理方法。shell

  • 定義一個NSURLRequest對象,而且管理請求頭的拼裝。數組

  • 對於每個網絡請求,經過一個SDWebImageDownloaderOperation自定義的NSOperation來操做網絡下載。緩存

  • 管理網絡加載過程和完成時候的回調工做。經過addProgressCallback實現。cookie

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之間的下載順序、作處理。session

/**
 * 當圖片下載完成之後,加壓縮圖片之後再換成。這樣能夠提高性能可是會佔用更多的存儲空間。
 * 模式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地址

相關文章
相關標籤/搜索