iOS源碼解析—SDWebImage(SDWebImageDownloader)

概述

本篇分析一下SDWebImage中負(fù)責(zé)下載圖片數(shù)據(jù)的相關(guān)代碼,SDWebImageDownloader和SDWebImageDownloaderOperation。

SDWebImageDownloader

SDWebImageDownloader是管理下載圖片數(shù)據(jù)的類,初始化方法代碼注釋如下:

- (id)init {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES; //是否解壓圖片
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new]; //下載的隊(duì)列
        _downloadQueue.maxConcurrentOperationCount = 6;
        _URLCallbacks = [NSMutableDictionary new]; //請(qǐng)求回調(diào)dic
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy]; //請(qǐng)求webp圖片數(shù)據(jù),報(bào)文頭部accept多包含image/webp字段
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); //
        _downloadTimeout = 15.0; //請(qǐng)求圖片數(shù)據(jù)超時(shí)超時(shí)時(shí)間
        //初始化NSURLSessionConfiguration對(duì)象
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        sessionConfig.timeoutIntervalForRequest = _downloadTimeout; //設(shè)置超時(shí)時(shí)間

        self.session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                     delegate:self
                                                delegateQueue:nil]; //創(chuàng)建session
    }
    return self;
}

該方法初始化了一些參數(shù)用于網(wǎng)絡(luò)請(qǐng)求,SDWebImageDownloader是通過(guò)NSURLSession的方式請(qǐng)求數(shù)據(jù)。同時(shí)提供了一些方法用于外部設(shè)置。代碼注釋如下:

//設(shè)置請(qǐng)求報(bào)文頭部
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
    if (value) {
        self.HTTPHeaders[field] = value;
    } else {
        [self.HTTPHeaders removeSafeObjectForKey:field];
    }
}
//獲取設(shè)置的報(bào)文頭部
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    return self.HTTPHeaders[field];
}
//設(shè)置下載隊(duì)列的最大并發(fā)個(gè)數(shù)
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
//獲取下載隊(duì)列的當(dāng)前operation個(gè)數(shù)
- (NSUInteger)currentDownloadCount {
    return _downloadQueue.operationCount;
}
//獲取下載隊(duì)列的最大并發(fā)個(gè)數(shù)
- (NSInteger)maxConcurrentDownloads {
    return _downloadQueue.maxConcurrentOperationCount;
}

SDWebImageDownloader最主要的方法是downloadImageWithURL:options:progress:completed:方法,調(diào)用該方法開始下載數(shù)據(jù)。下面主要分析一下具體執(zhí)行步驟:

  1. 首先調(diào)用addProgressCallback:completedBlock:forURL:createCallback:方法為當(dāng)前網(wǎng)絡(luò)請(qǐng)求關(guān)聯(lián)回調(diào)block,包括請(qǐng)求進(jìn)度的回調(diào)和請(qǐng)求結(jié)束的回調(diào),主要代碼如下:

          dispatch_barrier_sync(self.barrierQueue, ^{
                  BOOL first = NO;
                  if (!self.URLCallbacks[url]) {
                      self.URLCallbacks[url] = [NSMutableArray new]; //url對(duì)應(yīng)block數(shù)組
                      first = YES;
                  }
                  NSMutableArray *callbacksForURL = self.URLCallbacks[url];
                  NSMutableDictionary *callbacks = [NSMutableDictionary new];
                 //設(shè)置當(dāng)前請(qǐng)求進(jìn)度progress回調(diào)block和請(qǐng)求完成回調(diào)block
                  if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
                  if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
                  [callbacksForURL addObject:callbacks]; //添加callbacks
                  self.URLCallbacks[url] = callbacksForURL;
                  if (first) {
                      createCallback(); //執(zhí)行createCallback
                  }
              });
    
      該方法允許設(shè)置一組回調(diào)block,key是請(qǐng)求的url,數(shù)組中每個(gè)元素又是一個(gè)dictionary,包含請(qǐng)求進(jìn)度progress回調(diào)block和請(qǐng)求完成回調(diào)block,最后執(zhí)行createCallback。
    
  2. downloadImageWithURL方法接著在createCallback中構(gòu)建請(qǐng)求報(bào)文,代碼如下:

    //創(chuàng)建request對(duì)象
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
    //設(shè)置參數(shù)
    request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    request.HTTPShouldUsePipelining = YES;
    

    首先創(chuàng)建request對(duì)象,根據(jù)option值來(lái)決定本次網(wǎng)絡(luò)請(qǐng)求的緩存策略,option是SDWebImageDownloaderOptions類型的枚舉值,通過(guò)|的方式多選。SDWebImage默認(rèn)忽略NSURLCache緩存機(jī)制,即request的cachePolicy是NSURLRequestReloadIgnoringLocalCacheData,而是用自定義的SDImageCache來(lái)緩存數(shù)據(jù),上一篇文章分析了SDImageCache,如果option設(shè)置了SDWebImageDownloaderUseNSURLCache,則啟用NSURLCache緩存機(jī)制,并且設(shè)置cachePolicy為NSURLRequestUseProtocolCachePolicy,即通過(guò)服務(wù)端響應(yīng)報(bào)文的緩存策略來(lái)決定本地NSURLCache是否緩存數(shù)據(jù)。另外還支持pipelining和處理cookies。

  3. downloadImageWithURL方法接著創(chuàng)建一個(gè)SDWebImageDownloaderOperation類型的NSOperation對(duì)象,將本次網(wǎng)絡(luò)請(qǐng)求作為一個(gè)operation執(zhí)行。代碼注釋如下:

    //創(chuàng)建一個(gè)operation對(duì)象
    operation = [[wself.operationClass alloc] initWithRequest:request
                                                     inSession:self.session
                                                              options:options                                                 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
     SDWebImageDownloader *sself = wself;
     if (!sself) return;
     __block NSArray *callbacksForURL;
     dispatch_sync(sself.barrierQueue, ^{
         callbacksForURL = [sself.URLCallbacks[url] copy]; //取出callback數(shù)組
     });
     for (NSDictionary *callbacks in callbacksForURL) { //遍歷callback數(shù)組
         dispatch_async(dispatch_get_main_queue(), ^{
             SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; //取出progress的callback
             if (callback) callback(receivedSize, expectedSize); //執(zhí)行block
         });
     }
    }
    completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
     SDWebImageDownloader *sself = wself;
     if (!sself) return;
     __block NSArray *callbacksForURL;
     dispatch_barrier_sync(sself.barrierQueue, ^{
         callbacksForURL = [sself.URLCallbacks[url] copy];
         if (finished) {
             [sself.URLCallbacks removeObjectForKey:url]; //刪除回調(diào)
         }
     });
     for (NSDictionary *callbacks in callbacksForURL) { //遍歷callback數(shù)組
         SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
         if (callback) callback(image, data, error, finished); //執(zhí)行block
     }
    }
    cancelled:^{
     SDWebImageDownloader *sself = wself;
     if (!sself) return;
     dispatch_barrier_async(sself.barrierQueue, ^{
         [sself.URLCallbacks removeObjectForKey:url]; //刪除url
     });
    }];
    operation.shouldDecompressImages = wself.shouldDecompressImages; //圖片數(shù)據(jù)下載完成后是否要解壓
    

    請(qǐng)求圖片數(shù)據(jù)過(guò)程中觸發(fā)progress回調(diào),receivedSize是當(dāng)前接收數(shù)據(jù)的大小,expectedSize是數(shù)據(jù)總大小,請(qǐng)求完成觸發(fā)completed回調(diào),刪除url對(duì)應(yīng)的相關(guān)callback,并且執(zhí)行SDWebImageDownloaderCompletedBlock。當(dāng)operation被cancel,觸發(fā)cancelled回調(diào),刪除url對(duì)應(yīng)的相關(guān)callback。

  4. 最后將operation加入downloadQueue隊(duì)列中,開始執(zhí)行當(dāng)前網(wǎng)絡(luò)請(qǐng)求任務(wù)。同時(shí)設(shè)置了operation的優(yōu)先級(jí)和依賴關(guān)系。

由于SDWebImageDownloader將self設(shè)置為urlsession的delegate,當(dāng)發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),觸發(fā)urlsession的回調(diào)方法。SDWebImageDownloader實(shí)現(xiàn)了urlsession的相關(guān)代理方法,但是邏輯交給當(dāng)前dataTask對(duì)應(yīng)的SDWebImageDownloaderOperation對(duì)象來(lái)處理,通過(guò)operationWithTask找到dataTask對(duì)應(yīng)的operation。

- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
    SDWebImageDownloaderOperation *returnOperation = nil;
    for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations)     {
        //遍歷downloadQueue中當(dāng)前所有operation,匹配task
        if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
            returnOperation = operation;
            break;
        }
    }
    return returnOperation; //返回
}

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation是繼承NSOperation的類,對(duì)應(yīng)一次網(wǎng)絡(luò)請(qǐng)求任務(wù),首先通過(guò)初始化方法初始化一些參數(shù)設(shè)置,參數(shù)值有SDWebImageDownloader傳入。

start方法

當(dāng)operation加入downloadQueue中,觸發(fā)start方法,相關(guān)代碼注釋如下:

- (void)start {
    @synchronized (self) { //加鎖,多線程數(shù)據(jù)同步
        if (self.isCancelled) { //如果operation被取消
            self.finished = YES; //則operation結(jié)束
            [self reset];
            return;
        }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        //如果當(dāng)前operation標(biāo)記為在app進(jìn)入后臺(tái)時(shí)繼續(xù)下載圖片數(shù)據(jù)
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            //app進(jìn)入后允許執(zhí)行一段時(shí)間,超過(guò)期限執(zhí)行ExpirationHandler
            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 (!self.unownedSession) { //如果沒(méi)有在初始化方法設(shè)置unownedSession,則創(chuàng)建一個(gè)urlsession對(duì)象
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
            session = self.ownedSession;
        }
        //通過(guò)session創(chuàng)建一個(gè)dataTask
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES; //當(dāng)前operation標(biāo)記為執(zhí)行狀態(tài)
        self.thread = [NSThread currentThread]; //獲取當(dāng)前的線程
    }
    [self.dataTask resume]; //開始執(zhí)行dataTask

    if (self.dataTask) {
        if (self.progressBlock) { //當(dāng)前進(jìn)度為0
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }
    ...
}

該方法主要開啟一個(gè)NSURLSessionTask,并且調(diào)用resume方法執(zhí)行task,開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求。

控制operation生命周期

提供了setFinished:方法,setExecuting:方法控制operation的狀態(tài),實(shí)現(xiàn)是修改operation的狀態(tài)值,然后手動(dòng)拋通知給外部。同時(shí)提供了cancel和cancelInternal方法取消當(dāng)前operation。代碼注釋如下:

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel]; //取消operation
    if (self.cancelBlock) self.cancelBlock();
    //取消當(dāng)前網(wǎng)絡(luò)請(qǐng)求
    if (self.dataTask) {
        [self.dataTask cancel];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });
        //operation狀態(tài)為結(jié)束
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }
    [self reset]; //相關(guān)屬性置為nil
}
NSURLSessionDataDelegate

網(wǎng)絡(luò)請(qǐng)求執(zhí)行的過(guò)程中,觸發(fā)NSURLSession的相關(guān)代理方法,SDWebImageDownloader是delegate,實(shí)現(xiàn)了相關(guān)代理方法,會(huì)調(diào)用operation來(lái)執(zhí)行。operation實(shí)現(xiàn)了以下4個(gè)方法:

  1. -(void)URLSession:dataTask:didReceiveResponse:completionHandler:方法

    當(dāng)請(qǐng)求建立連接時(shí),服務(wù)器發(fā)送響應(yīng)給客戶端,觸發(fā)該方法,代碼注釋如下:
    
        - (void)URLSession:(NSURLSession *)session
                  dataTask:(NSURLSessionDataTask *)dataTask
        didReceiveResponse:(NSURLResponse *)response
         completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
            //根據(jù)服務(wù)端響應(yīng)的statusCode執(zhí)行不同邏輯
            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;
                if (self.progressBlock) {
                    self.progressBlock(0, expected);
                }
                //創(chuàng)建imageData接收數(shù)據(jù)
                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];
                if (code == 304) {
                    [self cancelInternal]; //304表示服務(wù)端圖片沒(méi)有更新,結(jié)束operation,用緩存中的圖片數(shù)據(jù)
                } else {
                    [self.dataTask cancel];//請(qǐng)求發(fā)生錯(cuò)誤,取消本次請(qǐng)求,
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
                });
                
                if (self.completedBlock) {
                    self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
                }
                [self done]; //結(jié)束operation
            }
            if (completionHandler) {
                completionHandler(NSURLSessionResponseAllow);
            }
        }
    

    該方法發(fā)生在客戶端收到服務(wù)端的響應(yīng),但是還沒(méi)開始傳輸數(shù)據(jù)的時(shí)候,如果相應(yīng)報(bào)文的statusCode小于400且不是304,則說(shuō)明連接正常,則創(chuàng)建imageData結(jié)束數(shù)據(jù)。如果statusCode是304,說(shuō)明網(wǎng)絡(luò)請(qǐng)求開啟緩存功能,且客戶端的請(qǐng)求報(bào)文中帶有上次緩存到本地的lastModified和ETag信息,服務(wù)端在對(duì)比本地資源和報(bào)文中的字段,發(fā)現(xiàn)資源沒(méi)有修改后,返回304,且不返回響應(yīng)的報(bào)文body數(shù)據(jù)。具體可以參考這篇文章。這種情況下不創(chuàng)建imageData接收數(shù)據(jù),直接取消dataTask,結(jié)束operation,即使用緩存中的圖片數(shù)據(jù)。如果statusCode不是304,則說(shuō)明請(qǐng)求失敗,取消dataTask,最后在done方法中結(jié)束operation。

  2. -(void)URLSession: dataTask: didReceiveData:方法

    當(dāng)開始下載數(shù)據(jù)時(shí),觸發(fā)該方法,如果數(shù)據(jù)比較多,會(huì)不斷觸發(fā),代碼注釋如下:

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        [self.imageData appendData:data]; //1.拼接數(shù)據(jù)
         if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
            //當(dāng)前已經(jīng)下載的數(shù)據(jù)大小
            const NSInteger totalSize = self.imageData.length;
            CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
            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);
                    orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; //圖片方向
                }
            }
            //圖像寬度和高度不為0,還沒(méi)接受完數(shù)據(jù)
            if (width + height > 0 && totalSize < self.expectedSize) {
                CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    
    #ifdef TARGET_OS_IPHONE
                if (partialImageRef) {
                    const size_t partialHeight = CGImageGetHeight(partialImageRef);
                    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
                     //創(chuàng)建context
                    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);
                         //取出位圖數(shù)據(jù)
                        partialImageRef = CGBitmapContextCreateImage(bmContext);
                        CGContextRelease(bmContext);
                    }
                    else {
                        CGImageRelease(partialImageRef);
                        partialImageRef = nil;
                    }
                }
    #endif
    
                if (partialImageRef) {
                     //創(chuàng)建image對(duì)象
                    UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
                    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                     //縮放圖片
                    UIImage *scaledImage = [self scaledImageForKey:key image:image];
                     //需要解碼
                    if (self.shouldDecompressImages) {
                        image = [UIImage decodedImageWithImage:scaledImage];
                    }
                    else {
                        image = scaledImage;
                    }
                    CGImageRelease(partialImageRef);
                    dispatch_main_sync_safe(^{
                        if (self.completedBlock) {
                            self.completedBlock(image, nil, nil, NO);
                        }
                    });
                }
            }
            CFRelease(imageSource);
        }
        if (self.progressBlock) {
            self.progressBlock(self.imageData.length, self.expectedSize);
        }
    }
    

    首先將拼接數(shù)據(jù),如果option包含SDWebImageDownloaderProgressiveDownload,即支持邊下載邊展示圖片,首先獲取圖片的寬、高、方向等信息,然后創(chuàng)建上下文對(duì)象bmContext,并調(diào)用CGContextDrawImage方法繪制圖像,最后生成UIImage對(duì)象,通過(guò)completedBlock返回。

  3. -(void)URLSession: dataTask: willCacheResponse: completionHandler:方法

    當(dāng)服務(wù)器緩存數(shù)據(jù)到本地時(shí),觸發(fā)該方法,代碼注釋如下:

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
     willCacheResponse:(NSCachedURLResponse *)proposedResponse
     completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
        responseFromCached = NO; //不是從緩存中取數(shù)據(jù)
        NSCachedURLResponse *cachedResponse = proposedResponse;
        if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
            cachedResponse = nil; //不緩存數(shù)據(jù)
        }
        if (completionHandler) {
            completionHandler(cachedResponse);
        }
    }
    

    首先將responseFromCached置為NO,說(shuō)明本次數(shù)據(jù)不是200 from cache,而是從服務(wù)器下載的,然后判斷cachePolicy如果是NSURLRequestReloadIgnoringLocalCacheData,說(shuō)明采用的緩存策略是忽略本地NSURLCache緩存,這個(gè)時(shí)候不將數(shù)據(jù)存入本地。上文分析到SDWebImage默認(rèn)忽略NSURLCache緩存,即request的cachePolicy是NSURLRequestReloadIgnoringLocalCacheData,所以該方法不將cachedResponse存入本地。(勘誤:經(jīng)過(guò)實(shí)驗(yàn),無(wú)論是200 from cache還是200,都會(huì)觸發(fā)NSURLSession的該方法,而NSURLConnection不會(huì)觸發(fā))。

  4. -(void)URLSession: task: didCompleteWithError:方法

    當(dāng)數(shù)據(jù)下載完成時(shí),觸發(fā)該方法,代碼注釋如下:

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        @synchronized(self) {
            self.thread = nil;
            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) {
            if (self.completedBlock) {
                self.completedBlock(nil, nil, error, YES);
            }
        } else {
            SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
            
            if (completionBlock) {
                if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) { //如果緩存中存在數(shù)據(jù),且沒(méi)有更新緩存,則返回nil,即圖片數(shù)據(jù)不更新
                    completionBlock(nil, nil, nil, YES);
                } else if (self.imageData) { //存在數(shù)據(jù),處理數(shù)據(jù)
                    UIImage *image = [UIImage sd_imageWithData:self.imageData];
                    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                    image = [self scaledImageForKey:key image:image];
                    
                    // GIF圖不解壓
                    if (!image.images) {
                        if (self.shouldDecompressImages) {
                            image = [UIImage decodedImageWithImage:image];//解壓圖片
                        }
                    }
                    if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                        completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
                    }
                    else {
                        completionBlock(image, self.imageData, nil, YES); //返回圖片
                    }
                } else {
                    completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
                }
            }
        }
        self.completionBlock = nil;
        [self done]; //完成本次operation
    }
    

    該方法首先判斷緩存策略,如果設(shè)置了option是SDWebImageRefreshCached(參考SDWebImageManager中的代碼),則downloadOption是SDWebImageDownloaderIgnoreCachedResponse和SDWebImageDownloaderUseNSURLCache,說(shuō)明使用NSURLCache的緩存機(jī)制來(lái)決定圖片的更新,通過(guò)服務(wù)器的cache-control字段來(lái)控制,具體情況分析如下:

    一、第一次下載圖片:服務(wù)器cache-control指定緩存時(shí)間,會(huì)將response存入NSURLCache,同時(shí)會(huì)進(jìn)入else if(self.imageData)中,將數(shù)據(jù)轉(zhuǎn)成圖片,并調(diào)用completionBlock()回調(diào),將圖片顯示出來(lái),并存入SDWebImageCache中。

    二、第二次下載相同url的圖片,如果本地緩存未過(guò)期,即NSURLCache中存在緩存數(shù)據(jù),responseFromCached=YES,同時(shí)由于設(shè)置了SDWebImageDownloaderIgnoreCachedResponse選項(xiàng),則completionBlock回調(diào)nil給外層。如果本地緩存過(guò)期則從服務(wù)端重新下載數(shù)據(jù),responseFromCached=NO,進(jìn)入else if(self.imageData)中,和(1)一樣處理。

    這種機(jī)制可以通過(guò)實(shí)現(xiàn)相同url圖片的更新,而不是SDWebImage默認(rèn)的機(jī)制,一個(gè)url對(duì)應(yīng)一張圖片,如果下載到本地(SDWebImageCache中存儲(chǔ)),則從緩存中取,不再下載。

    但是讓我疑惑的是SD實(shí)現(xiàn)NSURLSession的-(void)URLSession: dataTask: willCacheResponse: completionHandler:方法,每次都會(huì)觸發(fā),無(wú)論數(shù)據(jù)是否是從服務(wù)器下載還是緩存中取,導(dǎo)致responseFromCached=NO,每次都會(huì)進(jìn)入else if (self.imageData)這個(gè)邏輯分支。之前老的SD版本基于NSURLConnection,如果是200 from cache,不會(huì)觸發(fā)類似的willCacheResponse方法。這里不太清楚作者的用意。

  5. -(void)URLSession: task:didReceiveChallenge: completionHandler:方法

    當(dāng)發(fā)送HTTPS的url時(shí),觸發(fā)該方法用于校驗(yàn)服務(wù)端下發(fā)的證書。具體不進(jìn)行分析。

    ?

    ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容