SDWebImage源碼解析(四)

1 概述

這篇博文將分析SDWebImageDownloaderSDWebImageDownloaderOperation糯彬。SDWebImage通過這兩個(gè)類處理圖片的網(wǎng)絡(luò)加載崔拥。SDWebImageManager通過屬性imageDownloader持有SDWebImageDownloader并且調(diào)用它的downloadImageWithURL來從網(wǎng)絡(luò)加載圖片。SDWebImageDownloader實(shí)現(xiàn)了圖片加載的具體處理涵亏,如果圖片在緩存存在則從緩存區(qū),如果緩存不存在蒲凶,則直接創(chuàng)建一個(gè)SDWebImageDownloaderOperation對(duì)象來下載圖片气筋。管理NSURLRequest對(duì)象請(qǐng)求頭的封裝、緩存旋圆、cookie的設(shè)置宠默。加載選項(xiàng)的處理等功能。管理Operation之間的依賴關(guān)系灵巧。SDWebImageDownloaderOperation是一個(gè)自定義的并行Operation子類搀矫。這個(gè)類主要實(shí)現(xiàn)了圖片下載的具體操作、以及圖片下載完成以后的圖片解壓縮刻肄、Operation生命周期管理等瓤球。

2 SDWebImageDownloader分析

SDWebImageDownlaoder是一個(gè)單列對(duì)象,主要做了如下工作:

  • 定義了SDWebImageDownloaderOptions這個(gè)枚舉屬性敏弃,通過這個(gè)枚舉屬性來設(shè)置圖片從網(wǎng)絡(luò)加載的不同情況卦羡。
  • 定義并管理了NSURLSession對(duì)象,通過這個(gè)對(duì)象來做網(wǎng)絡(luò)請(qǐng)求,并且實(shí)現(xiàn)對(duì)象的代理方法绿饵。
  • 定義一個(gè)NSURLRequest對(duì)象欠肾,并且管理請(qǐng)求頭的拼裝。
  • 對(duì)于每一個(gè)網(wǎng)絡(luò)請(qǐng)求,通過一個(gè)SDWebImageDownloaderOperation自定義的NSOperation來操作網(wǎng)絡(luò)下載拟赊。
  • 管理網(wǎng)絡(luò)加載過程和完成時(shí)候的回調(diào)工作刺桃。通過addProgressCallback實(shí)現(xiàn)。

2.1 SDWebImageDownloaderOptions枚舉類型

可以通過這個(gè)枚舉類型來控制網(wǎng)絡(luò)加載要门、請(qǐng)求頭虏肾、緩存策略等。

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    SDWebImageDownloaderProgressiveDownload = 1 << 1,
    /*
     *默認(rèn)情況下欢搜,http請(qǐng)求阻止使用NSURLCache對(duì)象封豪。如果設(shè)置了這個(gè)標(biāo)記,則NSURLCache會(huì)被http請(qǐng)求使用炒瘟。
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,
    /*
     *如果image/imageData是從NSURLCache返回的吹埠。則completion這個(gè)回調(diào)會(huì)返回nil。
     */
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    /*
     *如果app進(jìn)入后臺(tái)模式疮装,是否繼續(xù)下載缘琅。這個(gè)是通過在后臺(tái)申請(qǐng)時(shí)間來完成這個(gè)操作。如果指定的時(shí)間范圍內(nèi)沒有完成廓推,則直接取消下載刷袍。
     */
    SDWebImageDownloaderContinueInBackground = 1 << 4,
    /*
     處理緩存在`NSHTTPCookieStore`對(duì)象里面的cookie。通過設(shè)置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`來實(shí)現(xiàn)的樊展。
     */
    SDWebImageDownloaderHandleCookies = 1 << 5,
    /*
     *允許非信任的SSL證書請(qǐng)求呻纹。
     *在測(cè)試的時(shí)候很有用。但是正式環(huán)境要小心使用专缠。
     */
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
    /*
     * 默認(rèn)情況下雷酪,圖片加載的順序是根據(jù)加入隊(duì)列的順序加載的。但是這個(gè)標(biāo)記會(huì)把任務(wù)加入隊(duì)列的最前面涝婉。
     */
    SDWebImageDownloaderHighPriority = 1 << 7,
    /*
     *默認(rèn)情況下哥力,圖片會(huì)按照他的原始大小來解碼顯示。這個(gè)屬性會(huì)調(diào)整圖片的尺寸到合適的大小根據(jù)設(shè)備的內(nèi)存限制墩弯。
     *如果`SDWebImageProgressiveDownload`標(biāo)記被設(shè)置了吩跋,則這個(gè)flag不起作用。
     */
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};

2.2 SDWebImageDownloader的屬性和初始化

可以通過它的屬性對(duì)最大并行下載數(shù)量渔工、超時(shí)時(shí)間锌钮、operation之間的下載順序、做處理涨缚。

/**
 * 當(dāng)圖片下載完成以后轧粟,加壓縮圖片以后再換成。這樣可以提升性能但是會(huì)占用更多的存儲(chǔ)空間脓魏。
 * 模式Y(jié)ES,如果你因?yàn)檫^多的內(nèi)存消耗導(dǎo)致一個(gè)奔潰兰吟,可以把這個(gè)屬性設(shè)置為NO。
 */
@property (assign, nonatomic) BOOL shouldDecompressImages;

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

/**
 當(dāng)前并行下載數(shù)量
 */
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
 下載超時(shí)時(shí)間設(shè)置
 */
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
 改變下載operation的執(zhí)行順序茂翔。默認(rèn)是FIFO混蔼。
 */
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

/**
 單列方法。返回一個(gè)單列對(duì)象
 @return 返回一個(gè)單列的SDWebImageDownloader對(duì)象
 */
+ (nonnull instancetype)sharedDownloader;
/**
 為圖片加載request設(shè)置一個(gè)SSL證書對(duì)象珊燎。
 */
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;

/**
 Basic認(rèn)證請(qǐng)求設(shè)置用戶名和密碼
 */
@property (strong, nonatomic, nullable) NSString *username;
@property (strong, nonatomic, nullable) NSString *password;

/**
 * 為http請(qǐng)求設(shè)置header惭嚣。
 * 每一request執(zhí)行的時(shí)候,這個(gè)Block都會(huì)被執(zhí)行悔政。用于向http請(qǐng)求添加請(qǐng)求域晚吞。
 */
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
 初始化一個(gè)請(qǐng)求對(duì)象

 @param sessionConfiguration NSURLSessionTask初始化配置
 @return 返回一個(gè)SDWebImageDownloader對(duì)象
 */
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
/**
 設(shè)置請(qǐng)求頭域

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

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

/**
 最后一個(gè)添加的Operation
 */
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;

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

/**
 用于記錄url和他對(duì)應(yīng)的SDWebImageDownloaderOperation對(duì)象谋国。
 */
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;

/**
 請(qǐng)求頭域字典
 */
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
/**
 通過這個(gè)`NSURLSession`創(chuàng)建請(qǐng)求
 */
@property (strong, nonatomic) NSURLSession *session;

2.2 downloadImageWithURL方法

這個(gè)方法是SDWebImageDownloader的核心方法槽地。SDWebImageManager通過這個(gè)方法來實(shí)現(xiàn)圖片從網(wǎng)絡(luò)加載。

/**
 新建一個(gè)SDWebImageDownloadOperation對(duì)象來來做具體的下載操作芦瘾。同時(shí)指定緩存策略捌蚊、cookie策略、自定義請(qǐng)求頭域等近弟。

 @param url url
 @param options 加載選項(xiàng)
 @param progressBlock 進(jìn)度progress
 @param completedBlock 完成回調(diào)
 @return 返回一個(gè)SDWebImageDownloadToken缅糟,用于關(guān)聯(lián)一個(gè)請(qǐng)求
 */
- (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同時(shí)緩存。我們默認(rèn)不允許image對(duì)象的NSURLCache對(duì)象祷愉。
         具體緩存策略參考http://www.reibang.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;
        //添加自定義請(qǐng)求頭
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }
        //初始化一個(gè)自定義NSOperation對(duì)象
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        //是否解壓縮返回的圖片
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        //指定驗(yàn)證信息
        if (sself.urlCredential) {
            //SSL驗(yàn)證
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            //Basic驗(yàn)證
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        //指定優(yōu)先級(jí)
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        //把operatin添加進(jìn)入NSOperationQueue中
        [sself.downloadQueue addOperation:operation];
        /*
         如果是LIFO這種模式窗宦,則需要手動(dòng)指定operation之間的依賴關(guān)系
         */
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            //如果是LIFO,則讓前面的operation依賴于最新添加的operation
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }
        return operation;
    }];
}
/**
 給下載過程添加進(jìn)度

 @param progressBlock 進(jìn)度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, ^{
        //看是否當(dāng)前url是否有對(duì)應(yīng)的Operation圖片加載對(duì)象
        SDWebImageDownloaderOperation *operation = self.URLOperations[url];
        //如果沒有迫摔,則直接創(chuàng)建一個(gè)。
        if (!operation) {
            //創(chuàng)建一個(gè)operation泥从。并且添加到URLOperation中句占。
            operation = createCallback();
            self.URLOperations[url] = operation;

            __weak SDWebImageDownloaderOperation *woperation = operation;
            //設(shè)置operation操作完成以后的回調(diào)
            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è)下載操作,使用cancel方法來處理

/**
 移除一個(gè)圖片加載操作

 @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的代理方法躯嫉,這里就不細(xì)講了纱烘。如果興趣可以參考AFNetWorking源碼分析。

3 SDWebImageDownloaderOperation分析

SDWebImageDownloaderOperation是一個(gè)自定義祈餐、并行的NSOperation子類擂啥。這個(gè)子類主要實(shí)現(xiàn)的功能有:

  • 由于只自定義的并行NSOperation,所以需要管理executing,finished等各種屬性的處理,并且手動(dòng)觸發(fā)KVO帆阳。
  • start(NSOperation規(guī)定哺壶,沒有為什么)方法里面實(shí)現(xiàn)主要邏輯。
  • NSURLSessionTaskDelegateNSURLSessionDataDelegate中處理數(shù)據(jù)的加載,以及進(jìn)度Block的處理山宾。
  • 如果unownedSession屬性因?yàn)槟撤N原因是nil至扰,則手動(dòng)初始化一個(gè)做網(wǎng)絡(luò)請(qǐng)求。
  • 在代理方法中對(duì)認(rèn)證资锰、數(shù)據(jù)拼裝敢课、完成回調(diào)Block做處理。
  • 通過發(fā)送SDWebImageDownloadStopNotification,SDWebImageDownloadFinishNotification,SDWebImageDownloadReceiveResponseNotification,SDWebImageDownloadStartNotification來通知Operation的狀態(tài)绷杜。

具體完整源碼如下:

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

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

/**
 自定義并行Operation需要管理的兩個(gè)屬性直秆。默認(rèn)是readonly的,我們這里通過聲明改為可修改的鞭盟。方便我們?cè)诤竺娌僮鳌? */
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;

/**
 存儲(chǔ)圖片數(shù)據(jù)
 */
@property (strong, nonatomic, nullable) NSMutableData *imageData;
/**
 通過SDWebImageDownloader傳過來圾结。所以這里是weak。因?yàn)樗峭ㄟ^SDWebImageDownloader管理的齿诉。
 */
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
/**
 如果unownedSession是nil疫稿,我們需要手動(dòng)創(chuàng)建一個(gè)并且管理他的生命周期和代理方法
 */
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;

/**
 dataTask對(duì)象
 */
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;

/**
 一個(gè)并行queue。用于控制數(shù)據(jù)的處理
 */
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;

#if SD_UIKIT

/**
 如果用戶設(shè)置了后臺(tái)繼續(xù)加載選線鹃两。則通過backgroundTask來繼續(xù)下載圖片
 */
@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];
        //默認(rèn)情況下遗座。_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添加進(jìn)度和回調(diào)Block

 @param progressBlock 進(jìn)度Block
 @param completedBlock 回調(diào)Block
 @return 回調(diào)字典
 */
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    //把Operation對(duì)應(yīng)的回調(diào)和進(jìn)度Block存入一個(gè)字典中
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    //把完成和進(jìn)度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需要重寫這個(gè)方法.在這個(gè)方法里面做具體的處理
 */
- (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模式,則設(shè)置一個(gè)backgroundTask
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                //background結(jié)束以后俊扳。做清理工作
                __strong __typeof (wself) sself = wself;
                if (sself) {
                    [sself cancel];
                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        NSURLSession *session = self.unownedSession;
        //如果SDWebImageDownloader傳入的session是nil途蒋,則自己手動(dòng)初始化一個(gè)。
        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;
    }
    //發(fā)送請(qǐng)求
    [self.dataTask resume];

    if (self.dataTask) {
        //第一次調(diào)用進(jìn)度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
}

/**
 如果要取消一個(gè)Operation馋记,就會(huì)調(diào)用這個(gè)方法号坡。
 */
- (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.
        //更新狀態(tài)
        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;
    }
}

/**
 需要手動(dòng)觸發(fā)_finished的KVO。這個(gè)是自定義并發(fā)`NSOperation`必須實(shí)現(xiàn)的梯醒。

 @param finished 改變狀態(tài)
 */
- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
/**
 需要手動(dòng)觸發(fā)_executing的KVO宽堆。這個(gè)是自定義并發(fā)`NSOperation`必須實(shí)現(xiàn)的。
 
 @param executing 改變狀態(tài)
 */
- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

/**
 返回YES茸习,表明這個(gè)NSOperation對(duì)象是并發(fā)的

 @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)) {
        //期望的總長(zhǎng)度
        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
        self.expectedSize = expected;
        //進(jìn)度回調(diào)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];
    }
    //這個(gè)表示允許繼續(xù)加載
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}
/*
 *會(huì)被多次調(diào)用籽慢。獲取圖片數(shù)據(jù)
 */
- (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
        //獲取已經(jīng)下載的數(shù)據(jù)長(zhǎng)度
        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的話表示還么有獲取到圖片的高度和寬度。我們可以通過數(shù)據(jù)來獲取圖片的寬度和高度
         *此時(shí)表示第一次收到圖片數(shù)據(jù)
         */
        if (width + height == 0) {
            //獲取圖片數(shù)據(jù)的屬性
            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
            }
        }
        /*
         * 這個(gè)表示已經(jīng)收到部分圖片數(shù)據(jù)并且還么有獲取到所有的圖片數(shù)據(jù)
         */
        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對(duì)應(yīng)的key
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                //根據(jù)原始圖片數(shù)據(jù)獲取對(duì)應(yīng)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 {
    //根據(jù)request的選項(xiàng)猫胁。決定是否緩存NSCachedURLResponse
    NSCachedURLResponse *cachedResponse = proposedResponse;

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

#pragma mark NSURLSessionTaskDelegate
/*
 網(wǎng)絡(luò)請(qǐng)求加載完成箱亿,在這里處理獲得的數(shù)據(jù)
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    @synchronized(self) {
        self.dataTask = nil;
        //發(fā)送圖片下載完成的通知
        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對(duì)應(yīng)的緩存Key
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                
                image = [self scaledImageForKey:key image:image];
                
                // Do not force decoding animated GIFs
                if (!image.images) {
                    //是否加壓縮圖片數(shù)據(jù)
                    if (self.shouldDecompressImages) {
                        //如果設(shè)置了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];
                        }
                    }
                }
                //構(gòu)建回調(diào)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];
}

/*
 驗(yàn)證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;
    //使用可信任證書機(jī)構(gòu)的證書
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        //如果SDWebImageDownloaderAllowInvalidSSLCertificates屬性設(shè)置了弃秆,則不驗(yàn)證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;
        }
    }
    //驗(yàn)證證書
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
#pragma mark Helper methods

#if SD_UIKIT || SD_WATCH

/**
 把整數(shù)轉(zhuǎn)換為對(duì)應(yīng)的枚舉值

 @param value 整數(shù)值
 @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對(duì)象獲取對(duì)應(yīng)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];
}
/**
 處理回調(diào)

 @param image UIImage數(shù)據(jù)
 @param imageData Image的data數(shù)據(jù)
 @param error 錯(cuò)誤
 @param finished 是否完成的標(biāo)記位
 */
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
                            imageData:(nullable NSData *)imageData
                                error:(nullable NSError *)error
                             finished:(BOOL)finished {
    //獲取key對(duì)應(yīng)的回調(diào)Block數(shù)組
    NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
    dispatch_main_async_safe(^{
        //調(diào)用回調(diào)
        for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
            completedBlock(image, imageData, error, finished);
        }
    });
}
@end

最后原文地址,demo地址髓帽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脑豹,隨后出現(xiàn)的幾起案子氢卡,更是在濱河造成了極大的恐慌,老刑警劉巖晨缴,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異峡捡,居然都是意外死亡击碗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門们拙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稍途,“玉大人,你說我怎么就攤上這事砚婆⌒蹬模” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵装盯,是天一觀的道長(zhǎng)坷虑。 經(jīng)常有香客問我,道長(zhǎng)埂奈,這世上最難降的妖魔是什么迄损? 我笑而不...
    開封第一講書人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮账磺,結(jié)果婚禮上芹敌,老公的妹妹穿的比我還像新娘。我一直安慰自己垮抗,他們只是感情好氏捞,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冒版,像睡著了一般液茎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辞嗡,一...
    開封第一講書人閱讀 52,793評(píng)論 1 314
  • 那天豁护,我揣著相機(jī)與錄音,去河邊找鬼欲间。 笑死楚里,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猎贴。 我是一名探鬼主播班缎,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼蝴光,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了达址?” 一聲冷哼從身側(cè)響起蔑祟,我...
    開封第一講書人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沉唠,沒想到半個(gè)月后疆虚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡满葛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年径簿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘀韧。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡篇亭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锄贷,到底是詐尸還是另有隱情译蒂,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布谊却,位于F島的核電站柔昼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏炎辨。R本人自食惡果不足惜岳锁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹦魔。 院中可真熱鬧激率,春花似錦、人聲如沸勿决。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽低缩。三九已至嘉冒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咆繁,已是汗流浹背讳推。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玩般,地道東北人银觅。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坏为,于是被迫代替她去往敵國(guó)和親究驴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镊绪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

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