原始碼閱讀:SDWebImage(十二)——SDWebImageDownloaderOperation

堯少羽發表於2018-06-29

該文章閱讀的SDWebImage的版本為4.3.3。

這個類負責影像下載的相關操作

1.SDWebImageOperation協議

這個協議中只有一個方法,就是取消方法。即實現這個協議的類必須實現取消方法。

- (void)cancel;
複製程式碼

2.全域性靜態常量

/**
 下載任務開始的通知名
 */
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;

/**
 下載任務已經接收到響應的通知名
 */
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;

/**
 下載任務結束的通知名
 */
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;

/**
 下載任務完成的通知名
 */
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
複製程式碼

3.SDWebImageDownloaderOperationInterface協議

這個協議定義了作為一個圖片下載操作類必須要提供的功能。如果你想自己自定義一個圖片下載操作類,需要繼承NSOperation類,並遵守這個協議。

/**
 以指定請求物件、會話物件和下載選項初始化圖片下載操作類
 */
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                              inSession:(nullable NSURLSession *)session
                                options:(SDWebImageDownloaderOptions)options;
複製程式碼
/**
 新增進度回撥block和完成回撥block
 */
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
複製程式碼
/**
 是否解壓縮影像物件
 */
- (BOOL)shouldDecompressImages;
複製程式碼
/**
 設定是否解壓縮影像物件
 */
- (void)setShouldDecompressImages:(BOOL)value;
複製程式碼
/**
 認證挑戰的證書
 */
- (nullable NSURLCredential *)credential;
複製程式碼
/**
 設定認證挑戰的證書
 */
- (void)setCredential:(nullable NSURLCredential *)value;
複製程式碼
/**
 取消指定token的下載操作
 */
- (BOOL)cancel:(nullable id)token;
複製程式碼

4.公共屬性

/**
 影像下載操作的請求物件
 */
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;

/**
 影像下載操作的任務物件
 */
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;

/**
 是否解壓縮圖片
 */
@property (assign, nonatomic) BOOL shouldDecompressImages;

/**
 該屬性已經廢棄
 */
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");

/**
 認證挑戰的證書
 */
@property (nonatomic, strong, nullable) NSURLCredential *credential;

/**
 下載選項
 */
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;

/**
 預計大小
 */
@property (assign, nonatomic) NSInteger expectedSize;

/**
 影像下載任務的響應物件
 */
@property (strong, nonatomic, nullable) NSURLResponse *response;
複製程式碼

5.公共方法

/**
 以指定請求物件、指定會話物件和下載操作物件
 */
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                              inSession:(nullable NSURLSession *)session
                                options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER;
複製程式碼
/**
 新增監聽程式block和監聽完成block
 */
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
複製程式碼
/**
 取消指定token的影像下載操作
 */
- (BOOL)cancel:(nullable id)token;
複製程式碼

6.私有巨集

/**
 利用GCD的訊號量實現加鎖的效果
 */
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
/**
 利用GCD的訊號量實現解鎖的效果
 */
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
複製程式碼

7.私有靜態常量

這三個靜態常量在iOS8中是定義在CFNetwork.framework中,這裡再定義一遍是為了相容iOS8而不引入CFNetwork.framework

#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
const float NSURLSessionTaskPriorityHigh = 0.75;
const float NSURLSessionTaskPriorityDefault = 0.5;
const float NSURLSessionTaskPriorityLow = 0.25;
#endif
複製程式碼

定義回撥block對應的key

static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
複製程式碼

8.型別定義

定義了一個key是NSString型別,value是id型別的可變字典

typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
複製程式碼

9.類擴充套件屬性

/**
 儲存回撥block
 */
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;

/**
 操作是否正在執行
 */
@property (assign, nonatomic, getter = isExecuting) BOOL executing;

/**
 操作是否執行完成
 */
@property (assign, nonatomic, getter = isFinished) BOOL finished;

/**
 儲存影像資料
 */
@property (strong, nonatomic, nullable) NSMutableData *imageData;

/**
 儲存SDWebImageDownloaderIgnoreCachedResponse的快取資料
 */
@property (copy, nonatomic, nullable) NSData *cachedData; 

/**
 一個弱引用的屬性儲存會話物件
 */
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;

/**
 一個強引用的屬性儲存會話物件
 */
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;

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

/**
 如果保證回撥執行緒安全的鎖
 */
@property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock; // a lock to keep the access to `callbackBlocks` thread-safe

/**
 用於影像解碼的佇列
 */
@property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue; 

#if SD_UIKIT
/**
 用於後臺下載的任務ID
 */
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif

/**
 編解碼器
 */
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder; 
複製程式碼

10.方法實現

10.1 私有方法

/**
 獲取指定金鑰的回撥block陣列
 */
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
    // 加鎖
    LOCK(self.callbacksLock);
    // 獲取指定金鑰下的回撥block陣列
    NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
    // 解鎖
    UNLOCK(self.callbacksLock);
    // 移除陣列中的[NSNull null]
    [callbacks removeObjectIdenticalTo:[NSNull null]];
    // 返回不可變陣列
    return [callbacks copy];
}
複製程式碼
/**
 取消
 */
- (void)cancelInternal {
    // 如果當前狀態是已完成就不繼續向下執行了
    if (self.isFinished) return;
    // 呼叫父類的取消方法
    [super cancel];

    // 如果任務還在
    if (self.dataTask) {
        // 取消任務
        [self.dataTask cancel];
        // 主佇列非同步回撥傳送通知
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
        });

        // 改變記錄狀態的屬性
        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 {
    // 加鎖
    LOCK(self.callbacksLock);
    // 移除所有儲存的回撥block
    [self.callbackBlocks removeAllObjects];
    // 解鎖
    UNLOCK(self.callbacksLock);
    // 任務物件置空
    self.dataTask = nil;
    
    // 會話物件取消並置空
    if (self.ownedSession) {
        [self.ownedSession invalidateAndCancel];
        self.ownedSession = nil;
    }
}
複製程式碼
/**
 根據字尾縮放圖片
 */
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
    return SDScaledImageForKey(key, image);
}
複製程式碼
/**
 應用進入後臺後是不繼續下載任務
 */
- (BOOL)shouldContinueWhenAppEntersBackground {
    return self.options & SDWebImageDownloaderContinueInBackground;
}
複製程式碼
/**
 下載出錯的情況下回撥完成block
 */
- (void)callCompletionBlocksWithError:(nullable NSError *)error {
    [self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
複製程式碼
/**
 下載成功的情況下回撥完成block
 */
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
                            imageData:(nullable NSData *)imageData
                                error:(nullable NSError *)error
                             finished:(BOOL)finished {
    NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
    dispatch_main_async_safe(^{
        for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
            completedBlock(image, imageData, error, finished);
        }
    });
}
複製程式碼

10.2 SDWebImageDownloaderOperationInterface協議方法

- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                              inSession:(nullable NSURLSession *)session
                                options:(SDWebImageDownloaderOptions)options {
    // 呼叫父類的初始化方法
    if ((self = [super init])) {
        // 儲存請求物件
        _request = [request copy];
        // 預設是解壓縮影像
        _shouldDecompressImages = YES;
        // 儲存下載選項
        _options = options;
        // 初始化可變陣列儲存回撥block
        _callbackBlocks = [NSMutableArray new];
        // 執行狀態設定為NO
        _executing = NO;
        // 完成狀態狀態設定為NO
        _finished = NO;
        // 預計大小預設為0
        _expectedSize = 0;
        // 用弱引用的屬性儲存會話物件
        _unownedSession = session;
        // 生成訊號量
        _callbacksLock = dispatch_semaphore_create(1);
        // 生成序列佇列
        _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
複製程式碼
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // 建立可變字典儲存進度回撥block和完成回撥block
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    // 加鎖
    LOCK(self.callbacksLock);
    // 用陣列儲存可變字典
    [self.callbackBlocks addObject:callbacks];
    // 解鎖
    UNLOCK(self.callbacksLock);
    // 返回可變字典
    return callbacks;
}
複製程式碼
- (BOOL)cancel:(nullable id)token {
    // 建立變數儲存是否需要取消,預設為NO
    BOOL shouldCancel = NO;
    // 加鎖
    LOCK(self.callbacksLock);
    // 移除token對應的回撥block
    [self.callbackBlocks removeObjectIdenticalTo:token];
    // 如果沒有回撥block就設定為YES
    if (self.callbackBlocks.count == 0) {
        shouldCancel = YES;
    }
    // 解鎖
    UNLOCK(self.callbacksLock);
    // 如果需要取消就取消
    if (shouldCancel) {
        [self cancel];
    }
    // 返回是否需要取消
    return shouldCancel;
}
複製程式碼

10.3 重寫父類NSOperation方法

- (nonnull instancetype)init {
    // 呼叫自定義初始化方法
    return [self initWithRequest:nil inSession:nil options:0];
}
複製程式碼
- (void)start {
    // 同步鎖
    @synchronized (self) {
        // 如果當前狀態是取消
        if (self.isCancelled) {
            // 完成狀態設定為YES
            self.finished = YES;
            // 呼叫重置
            [self reset];
            // 返回
            return;
        }

#if SD_UIKIT
        // 如果設定了後臺下載選項,就申請後臺任務
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;

                if (sself) {
                    // 取消任務
                    [sself cancel];

                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        // 獲取會話物件
        NSURLSession *session = self.unownedSession;
        // 如果沒獲取到會話物件
        if (!session) {
            // 建立一個會話物件並設定請求超時時間為15秒
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;
        }
        
        // 如果設定了忽略NSURLCache
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // 從繪畫物件中獲取快取資料
            NSURLCache *URLCache = session.configuration.URLCache;
            // 如果沒有就獲取單例
            if (!URLCache) {
                URLCache = [NSURLCache sharedURLCache];
            }
            // 獲取快取響應物件
            NSCachedURLResponse *cachedResponse;
            @synchronized (URLCache) {
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            // 獲取快取資料
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
            }
        }
        
        // 建立影像下載任務
        self.dataTask = [session dataTaskWithRequest:self.request];
        // 執行狀態設定為YES
        self.executing = YES;
    }

    if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
        // 設定下載任務的優先順序
        if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
            if (self.options & SDWebImageDownloaderHighPriority) {
                self.dataTask.priority = NSURLSessionTaskPriorityHigh;
            } else if (self.options & SDWebImageDownloaderLowPriority) {
                self.dataTask.priority = NSURLSessionTaskPriorityLow;
            }
        }
#pragma clang diagnostic pop
        // 啟動下載任務
        [self.dataTask resume];
        // 回撥進度block
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        // 主佇列非同步傳送通知
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
        });
    } else {
        // 如果下載任務建立失敗就回撥錯誤
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
        // 結束
        [self done];
        return;
    }

#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
}
複製程式碼
- (void)cancel {
    // 呼叫自定義的取消方法
    @synchronized (self) {
        [self cancelInternal];
    }
}
複製程式碼
- (void)setFinished:(BOOL)finished {
    // 手動實現KVO傳送
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
複製程式碼
- (void)setExecuting:(BOOL)executing {
    // 手動實現KVO傳送
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}
複製程式碼
- (BOOL)isConcurrent {
    // 始終返回YES
    return YES;
}
複製程式碼

10.3 NSURLSessionDataDelegate代理方法

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    // 建立變數儲存會話響應意向,預設為允許
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    // 建立變數儲存預期大小,預設為0
    NSInteger expected = (NSInteger)response.expectedContentLength;
    expected = expected > 0 ? expected : 0;
    // 屬性儲存預期大小
    self.expectedSize = expected;
    // 屬性儲存響應物件
    self.response = response;
    // 獲取響應狀態碼
    NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
    // 建立變數儲存是否有效:狀態碼比400小就有效
    BOOL valid = statusCode < 400;
    // 但是如果狀態碼是304並且沒有快取資料,就設定為無效
    if (statusCode == 304 && !self.cachedData) {
        valid = NO;
    }
    
    if (valid) {
        // 如果有效,回撥進度block
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, expected, self.request.URL);
        }
    } else {
        // 如果無效設定會話響應意向為取消
        disposition = NSURLSessionResponseCancel;
    }
    
    // 傳送通知
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
    });
    
    // 返回會話響應意向
    if (completionHandler) {
        completionHandler(disposition);
    }
}
複製程式碼
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 初始化屬性儲存影像資料
    if (!self.imageData) {
        self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
    }
    // 儲存下載的資料
    [self.imageData appendData:data];

    // 如果設定漸進式下載並且預期大小大於零
    if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
        // 獲取已經下載的影像資料
        __block NSData *imageData = [self.imageData copy];
        // 獲取已經下載的影像大小
        const NSInteger totalSize = imageData.length;
        // 如果已下載的影像大小不小於預期大小就是下載完成
        BOOL finished = (totalSize >= self.expectedSize);
        
        // 建立逐行編解碼器
        if (!self.progressiveCoder) {
            for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) {
                if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
                    [((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
                    self.progressiveCoder = [[[coder class] alloc] init];
                    break;
                }
            }
        }
        
        // 序列佇列非同步執行
        dispatch_async(self.coderQueue, ^{
            // 逐行解碼影像資料獲取影像物件
            UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
            if (image) {
                // 根據影像的地址獲取快取金鑰
                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                // 縮放影像
                image = [self scaledImageForKey:key image:image];
                // 如果需要解壓縮就解壓縮
                if (self.shouldDecompressImages) {
                    image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
                }
                
                // 回撥完成block
                [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
            }
        });
    }

    // 回撥進度block
    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 {
    
    NSCachedURLResponse *cachedResponse = proposedResponse;

    // 如果沒設定使用NSURLCache就將快取響應置空後返回
    if (!(self.options & SDWebImageDownloaderUseNSURLCache)) {
        cachedResponse = nil;
    }
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}
複製程式碼

10.4 NSURLSessionTaskDelegate代理方法

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 同步鎖
    @synchronized(self) {
        // 儲存下載任務的屬性置空
        self.dataTask = nil;
        // 傳送通知
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
            if (!error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
            }
        });
    }
    
    if (error) {
        // 如果出錯就回撥錯誤
        [self callCompletionBlocksWithError:error];
        // 完成
        [self done];
    } else {
        // 如果有完成回撥的block
        if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
            // 獲取到下載的影像資料
            __block NSData *imageData = [self.imageData copy];
            if (imageData) {
                // 如果設定了忽略快取響應選項,並且快取資料和下載資料相同
                if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
                    // 完成回撥返回空
                    [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
                    // 完成
                    [self done];
                } else {
                    // 序列佇列非同步執行
                    dispatch_async(self.coderQueue, ^{
                        // 解碼影像資料獲得影像物件
                        UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
                        // 根據影像地址獲取影像快取金鑰
                        NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                        // 縮放影像物件
                        image = [self scaledImageForKey:key image:image];
                        
                        // 如果影像是GIF和WebP格式的就不解碼
                        BOOL shouldDecode = YES;
                        if (image.images) {
                            shouldDecode = NO;
                        } else {
#ifdef SD_WEBP
                            SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
                            if (imageFormat == SDImageFormatWebP) {
                                shouldDecode = NO;
                            }
#endif
                        }
                        
                        // 如果需要解碼,並且需要解壓縮就進行解壓縮
                        if (shouldDecode) {
                            if (self.shouldDecompressImages) {
                                BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
                                image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
                            }
                        }
                        // 獲取影像大小
                        CGSize imageSize = image.size;
                        if (imageSize.width == 0 || imageSize.height == 0) {
                            // 如果影像沒有大小就回撥錯誤
                            [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                        } else {
                            // 如果影像有大小就回撥結果
                            [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
                        }
                        // 完成
                        [self done];
                    });
                }
            } else {
                // 如果沒有影像資料就回撥錯誤
                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
                // 完成
                [self done];
            }
        } else {
            // 沒有完成回撥block就呼叫完成方法
            [self done];
        }
    }
}
複製程式碼
- (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]) {
        // 如果證書驗證方法是信任伺服器
        if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
            // 如果設定了信任無效SSL證書意向就設定為預設
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        } else {
            // 如果沒設定,生成證書,意向是通過證書
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            disposition = NSURLSessionAuthChallengeUseCredential;
        }
    } else {
        if (challenge.previousFailureCount == 0) {
            // 如果失敗嘗試數為0
            if (self.credential) {
                // 如果有證書就設定意向為通過證書
                credential = self.credential;
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                // 如果沒有證書就設定意向為取消
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
        // 如果失敗嘗試數不為0,就設定意向為取消
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    
    // 返回意向和證書
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
複製程式碼

11.總結

這個類主要是對影像下載操作的處理。利用傳入的請求物件和會話物件建立下載任務,在下載任務代理方法中處理下載過程中的種種情況,通過傳入的回撥block反饋下載進度和下載結果。

原始碼閱讀系列:SDWebImage

原始碼閱讀:SDWebImage(一)——從使用入手

原始碼閱讀:SDWebImage(二)——SDWebImageCompat

原始碼閱讀:SDWebImage(三)——NSData+ImageContentType

原始碼閱讀:SDWebImage(四)——SDWebImageCoder

原始碼閱讀:SDWebImage(五)——SDWebImageFrame

原始碼閱讀:SDWebImage(六)——SDWebImageCoderHelper

原始碼閱讀:SDWebImage(七)——SDWebImageImageIOCoder

原始碼閱讀:SDWebImage(八)——SDWebImageGIFCoder

原始碼閱讀:SDWebImage(九)——SDWebImageCodersManager

原始碼閱讀:SDWebImage(十)——SDImageCacheConfig

原始碼閱讀:SDWebImage(十一)——SDImageCache

原始碼閱讀:SDWebImage(十二)——SDWebImageDownloaderOperation

原始碼閱讀:SDWebImage(十三)——SDWebImageDownloader

原始碼閱讀:SDWebImage(十四)——SDWebImageManager

原始碼閱讀:SDWebImage(十五)——SDWebImagePrefetcher

原始碼閱讀:SDWebImage(十六)——SDWebImageTransition

原始碼閱讀:SDWebImage(十七)——UIView+WebCacheOperation

原始碼閱讀:SDWebImage(十八)——UIView+WebCache

原始碼閱讀:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat

原始碼閱讀:SDWebImage(二十)——UIButton+WebCache

原始碼閱讀:SDWebImage(二十一)——UIImageView+WebCache/UIImageView+HighlightedWebCache

相關文章