SDWebImage源碼分析(二)

上一章解析到- (id)loadImageWithURL:(nullable NSURL *)url

options:(SDWebImageOptions)options

progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDInternalCompletionBlock)completedBlock 中self.imageCache? 調用 - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock doneBlock

回調doneBlock 執行

回調內容比較多,所以分部分貼代碼

第一段

if (operation.isCancelled) {

[self safelyRemoveOperationFromRunning:operation];

return;

}

- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {

@synchronized (self.runningOperations) {

if (operation) {

[self.runningOperations removeObject:operation];

}

}

}

這個很簡單 ,要是 線程被取消掉了,那么就從runningOperations 數組中將該操作移除

第二段

if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]))

要是沒有cachedImage? 或者 options 配置SDWebImageRefreshCached (刷新緩存) 或者是self.delegate 調用- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL; 返回YES 就往下執行操作

第三段 是假設第二段需要繼續執行的返回YES

if (cachedImage && options & SDWebImageRefreshCached) {

// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image

// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.

[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];

}

要是 上面都滿足 cachedImage && options & SDWebImageRefreshCached 還是進來的話我們調用下面這個函數

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation

completion:(nullable SDInternalCompletionBlock)completionBlock

image:(nullable UIImage *)image

data:(nullable NSData *)data

error:(nullable NSError *)error

cacheType:(SDImageCacheType)cacheType

finished:(BOOL)finished

url:(nullable NSURL *)url 這個函數后面分析

第四段 是假設第二段需要繼續執行的返回YES

// download if no image or requested to refresh anyway, and download allowed by delegate

SDWebImageDownloaderOptions downloaderOptions = 0;

if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;

if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;

if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;

if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;

if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;

if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;

if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;

if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;

if (cachedImage && options & SDWebImageRefreshCached) {

// force progressive off if image already cached but forced refreshing

downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;

// ignore image read from NSURLCache if image if cached but force refreshing

downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;

}

這些都是配置下載項 最后一項比較特殊,要是 有緩存圖片,并且配置刷新圖片,那么我們就需要配置下載選項,清除下載進度條 并且要忽略cached 配置。

第五段 是假設第二段需要繼續執行的返回YES

SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished){ ?..... ? }

這個就是開始調用imageDownLoader 進行下載操作了

進入函數- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url

options:(SDWebImageDownloaderOptions)options

progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock ?

這個函數分兩部分看我們以 《》標記

《1》第一部分是調用

[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{....}

進入該函數

- (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, ^{

SDWebImageDownloaderOperation *operation = self.URLOperations[url];

if (!operation) {

operation = createCallback();

self.URLOperations[url] = operation;

__weak SDWebImageDownloaderOperation *woperation = operation;

operation.completionBlock = ^{

dispatch_barrier_sync(self.barrierQueue, ^{

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;

}

第一步:檢查url 是不是nil 是nil 直接執行block 并且返回

第二步:調用GCD? dispatch_barrier_sync 切換到self.barrierQueue 線程操作?

第三步:檢查下載URLOperations 字典中有沒有這個url的 operation?

第四步:要是沒有,就調用createCallback 生成一個operation? 把 operation 放入 URLOperations 字段中,key 是url,并且給operation 完成的completionBlock 賦值。completionBlock block 調用的話會執行,切換到self.barrierQueue 線程,檢查self.URLOperations 中url 對應的value 是不是和現在的這個一樣,要是一樣的話,就沖線程字典中移除

第五步調用 - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock 。

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {

SDCallbacksDictionary *callbacks = [NSMutableDictionary new];

if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];

if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];

dispatch_barrier_async(self.barrierQueue, ^{

[self.callbackBlocks addObject:callbacks];

});

return callbacks;

}

這個函數 就是給opertion 數組中添加 一個字典,字典中包含兩個兩個回調函數,并且返回這個字典。

第六步 生成一個 SDWebImageDownloadToken 對象,改對象保存 請求url 和 url 進度條操作和完成操作

《2》第二部分是 callBack

__strong __typeof (wself) sself = wself;

NSTimeInterval timeoutInterval = sself.downloadTimeout;

if (timeoutInterval == 0.0) {

timeoutInterval = 15.0;

}

// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise

NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;

if (options & SDWebImageDownloaderUseNSURLCache) {

if (options & SDWebImageDownloaderIgnoreCachedResponse) {

cachePolicy = NSURLRequestReturnCacheDataDontLoad;

} else {

cachePolicy = NSURLRequestUseProtocolCachePolicy;

}

}

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];

request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);

request.HTTPShouldUsePipelining = YES;

if (sself.headersFilter) {

request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);

}

else {

request.allHTTPHeaderFields = sself.HTTPHeaders;

}

SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];

operation.shouldDecompressImages = sself.shouldDecompressImages;

if (sself.urlCredential) {

operation.credential = sself.urlCredential;

} else if (sself.username && sself.password) {

operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];

}

if (options & SDWebImageDownloaderHighPriority) {

operation.queuePriority = NSOperationQueuePriorityHigh;

} else if (options & SDWebImageDownloaderLowPriority) {

operation.queuePriority = NSOperationQueuePriorityLow;

}

[sself.downloadQueue addOperation:operation];

if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {

// Emulate LIFO execution order by systematically adding new operations as last operation's dependency

[sself.lastAddedOperation addDependency:operation];

sself.lastAddedOperation = operation;

}

return operation;

我看著現在感覺就是山路十八彎啊,繞來繞去,據我估計看博客的人也會繞暈。

沒事看不懂也要強行裝逼看。看完最后總結路線。

這個block 回調我們以<> 來標記

<1>第一部分:先判斷strongOperation 是否取消掉了。要是取消掉了啥呀不做

<2>第二部分:要是block參數 error 不是nil 那么就執行[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];? 并且檢查error 錯誤,不是NSURLErrorNotConnectedToInternet,NSURLErrorCancelled,NSURLErrorTimedOut,NSURLErrorInternationalRoamingOff,NSURLErrorDataNotAllowed,NSURLErrorCannotFindHost,NSURLErrorCannotConnectToHost,NSURLErrorNetworkConnectionLost那么就將該url 加入到failedURLs數組中。

<3>第三部分:檢查options? 是不是配置 SDWebImageRetryFailed 要是配置該參數

就將url從failure 數組中移除掉

<4>第四部分: options & SDWebImageRefreshCached && cachedImage && !downloadedImage? 如果配置 SDWebImageRefreshCached 并且 cachedImage 但是 downloadedImage 參數沒有值就不做任何處理。

<5>第五部分:downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]?

這么多條件,執行下面操作?

異步dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),并發線程執行[self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]獲取url

要是 transformedImage? 有并且 finished =yes的話,self.imageCache 緩存圖片

- (void)storeImage:(nullable UIImage *)image

imageData:(nullable NSData *)imageData

forKey:(nullable NSString *)key

toDisk:(BOOL)toDisk

completion:(nullable SDWebImageNoParamsBlock)completionBlock {

if (!image || !key) {

if (completionBlock) {

completionBlock();

}

return;

}

// if memory cache is enabled

if (self.config.shouldCacheImagesInMemory) {

NSUInteger cost = SDCacheCostForImage(image);

[self.memCache setObject:image forKey:key cost:cost];

}

if (toDisk) {

dispatch_async(self.ioQueue, ^{

@autoreleasepool {

NSData *data = imageData;

if (!data && image) {

SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];

data = [image sd_imageDataAsFormat:imageFormatFromData];

}

[self storeImageDataToDisk:data forKey:key];

}

if (completionBlock) {

dispatch_async(dispatch_get_main_queue(), ^{

completionBlock();

});

}

});

} else {

if (completionBlock) {

completionBlock();

}

}

}

分析下這個函數

1 :要是沒有image 或者key 如果有completionBlock 直接調用completionBlock

2:要還是配置shouldCacheImagesInMemory 那么緩存 image 到記憶緩存

3:要是toDisk 不為空,切換到ioQueue 線程,判斷是否有圖片或者data ,沒有data 但是有 image情況,那么就生成一個data,

- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {

NSData *imageData = nil;

if (self) {

#if SD_UIKIT || SD_WATCH

int alphaInfo = CGImageGetAlphaInfo(self.CGImage);

BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||

alphaInfo == kCGImageAlphaNoneSkipFirst ||

alphaInfo == kCGImageAlphaNoneSkipLast);

BOOL usePNG = hasAlpha;

// the imageFormat param has priority here. But if the format is undefined, we relly on the alpha channel

if (imageFormat != SDImageFormatUndefined) {

usePNG = (imageFormat == SDImageFormatPNG);

}

if (usePNG) {

imageData = UIImagePNGRepresentation(self);

} else {

imageData = UIImageJPEGRepresentation(self, (CGFloat)1.0);

}

#else

NSBitmapImageFileType imageFileType = NSJPEGFileType;

if (imageFormat == SDImageFormatGIF) {

imageFileType = NSGIFFileType;

} else if (imageFormat == SDImageFormatPNG) {

imageFileType = NSPNGFileType;

}

imageData = [NSBitmapImageRep representationOfImageRepsInArray:self.representations

usingType:imageFileType

properties:@{}];

#endif

}

return imageData;

}

用image 生成data 的函數中 ,檢查是否生成png data 還是生成jpg格式圖片。

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {

if (!imageData || !key) {

return;

}

[self checkIfQueueIsIOQueue];

if (![_fileManager fileExistsAtPath:_diskCachePath]) {

[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];

}

// get cache Path for image key

NSString *cachePathForKey = [self defaultCachePathForKey:key];

// transform to NSUrl

NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];

[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

// disable iCloud backup

if (self.config.shouldDisableiCloud) {

[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];

}

}

先檢查 當前線程是不是IOQueue 不是就輸出log,再堅持路徑,沒有路徑就生成路徑,生成圖片名字,將生成的路徑轉換成NSURL 。

將數據寫入磁盤, 如果shouldDisableiCloud 打開,就執行這個操作

[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];

NSURLIsExcludedFromBackupKey 屬性看這里

4:將data 寫入磁盤

5:有completionBlock 回到主線程調用completionBlock

6:其他情況,有completionBlock 回到主線程調用completionBlock

《3》第三部分:cancelblock?


operation.cancelBlock = ^{

[self.imageDownloader cancel:subOperationToken];

__strong __typeof(weakOperation) strongOperation = weakOperation;

[self safelyRemoveOperationFromRunning:strongOperation];

};

這個就是給operation.cancelBlock 賦值, 具體執行啥呢?

- (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];

}

});

}

切換到 barrierQueue 線程,找到 URLOperations 對應URL的SDWebImageDownloaderOperation

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

}

從 self.callbackBlocks 移除對象token 。要是數組的數量沒有就調用cancel 這個設計到nsopertion 的取消操作,網絡部分,暫時不討論。

- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {

@synchronized (self.runningOperations) {

if (operation) {

[self.runningOperations removeObject:operation];

}

}

}?

最后從self.runningOperations 移除數據。

第六段 我們看 self.imageDownloader 調用- (id)loadImageWithURL:(nullable NSURL *)url

options:(SDWebImageOptions)options

progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDInternalCompletionBlock)completedBlock 返回的block塊

else if (cachedImage) {

__strong __typeof(weakOperation) strongOperation = weakOperation;

[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];

[self safelyRemoveOperationFromRunning:operation];

}

要是有緩存,就執行 回調移除opertion

else {

// Image not in cache and download disallowed by delegate

__strong __typeof(weakOperation) strongOperation = weakOperation;

[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];

[self safelyRemoveOperationFromRunning:operation];

}

最后一種情況一樣調用上面的函數。

到此- (id)loadImageWithURL:(nullable NSURL *)url

options:(SDWebImageOptions)options

progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDInternalCompletionBlock)completedBlock分析完畢,據我估計看博客的人肯定看的一臉懵逼。沒關系,我和你一樣的狀態。

接著往下看。不看懂誓不罷休。


返回到- (void)sd_internalSetImageWithURL:(nullable NSURL *)url

placeholderImage:(nullable UIImage *)placeholder

options:(SDWebImageOptions)options

operationKey:(nullable NSString *)operationKey

setImageBlock:(nullable SDSetImageBlock)setImageBlock

progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDExternalCompletionBlock)completedBlock 函數中往下看。

[self sd_setImageLoadOperation:operation forKey:validOperationKey];?

在這個if 語句中最后一塊。將opertion?

- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {? ? if (key) {? ? ? ? [self sd_cancelImageLoadOperationWithKey:key];? ? ? ? if (operation) {? ? ? ? ? ? SDOperationsDictionary *operationDictionary = [self operationDictionary];? ? ? ? ? ? operationDictionary[key] = operation;? ? ? ? }? ? }}- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {? ? // Cancel in progress downloader from queue? ? SDOperationsDictionary *operationDictionary = [self operationDictionary];? ? id operations = operationDictionary[key];? ? if (operations) {? ? ? ? if ([operations isKindOfClass:[NSArray class]]) {? ? ? ? ? ? for (idoperation in operations) {? ? ? ? ? ? ? ? if (operation) {? ? ? ? ? ? ? ? ? ? [operation cancel];? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){? ? ? ? ? ? [(id) operations cancel];

}

[operationDictionary removeObjectForKey:key];

}

}

- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {

if (key) {

SDOperationsDictionary *operationDictionary = [self operationDictionary];

[operationDictionary removeObjectForKey:key];

}

}

- (SDOperationsDictionary *)operationDictionary {

SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);

if (operations) {

return operations;

}

operations = [NSMutableDictionary dictionary];

objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

return operations;

}


在- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key 函數中調用

- (SDOperationsDictionary *)operationDictionary

這個函數比較簡單,就是self 關聯一個引用字典,返回這個self關聯字典, 在字典里面找key關聯的值,如果找到了,再檢測改之是不是NSArray ,是就讓array里面的value 執行cancel操作,不是array 檢測是不是遵守SDWebImageOperation 協議,是就執行cancel操作,最后從dic移除該key

- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key這個函數就是取消所有operation的操作,再增加最近的opertion

到目前為止,應該已經在下載了,什么在下載?我特么都沒看見下載代碼。我一臉懵逼。

沒辦法,誰叫咱是菜鳥呢?只能打斷點實驗了。

全局搜索 addOperation 只有一個地方,我了個去。

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url

options:(SDWebImageDownloaderOptions)options

progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock

completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock函數中,- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock

completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock

forURL:(nullable NSURL *)url

createCallback:(SDWebImageDownloaderOperation *(^)())createCallback ?的回調函數忘記分析了

下面分析這個block

我們用 1> 標記

1> 首先獲取超時時間,默認是15s

2>設置緩存策略

3>生成request

4>配置request 相關參數

5>生成 SDWebImageDownloaderOperation?

6>配置賬號和密碼

7>將operation 增加到queue中

8>下載操作看怎么進出的

發起網絡請求就是這么簡單。那我們看看下載完成進行啥操作了吧

那網絡請求發出去了。那么看數據回來怎么操作的

看SDWebImageDownloaderOperation 函數數據回調

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

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

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

[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);

}

}

分步看

第一步:[self.imageData appendData:data]; 將數據追加到self.imageData;

第二步:判斷self.options & SDWebImageDownloaderProgressiveDownload && self.expectedSize>0是不是YES?

是的話,執行下面操作。self.expectedSize 是在響應頭獲取的

第三步:生成CGImageSourceRef 根據self.imageData

第四步 : 根據width + Height == 0 獲取圖片的width Height 還有orientation?

第五步: 要是 寬高有值了。 生成CGImageRef?

第六步 要是生成了 CGImageRef 那就生成bitMap的bmContext?

第七步 : 如果生成的bmContext 不是nil 就用bmContext 生成? partialImageRef image

第八步:要是 partialImageRef 轉換成UIImage

第九步:對圖片處理并返回

最后一步:要是有進度條。那就執行進度條

其實接受數據部分就是對不完整數據進行生成圖片加載。

當數據都接受完成的時候

- (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];

} else {

if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {

/**

*? If you specified to use `NSURLCache`, then the response you get here is what you need.

*? if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,

*? the response data will be nil.

*? So we don't need to check the cache option here, since the system will obey the cache option

*/

if (self.imageData) {

UIImage *image = [UIImage sd_imageWithData:self.imageData];

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

if (self.options & SDWebImageDownloaderScaleDownLargeImages) {

#if SD_UIKIT || SD_WATCH

image = [UIImage decodedAndScaledDownImageWithImage:image];

[self.imageData setData:UIImagePNGRepresentation(image)];

#endif

} else {

image = [UIImage decodedImageWithImage:image];

}

}

}

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];

}

第一步:發通知

第二步:判斷請求是否錯誤 error 就調用error回調

第三步:如果有完成回調 那么執行下面操作

第四步:要是self.imageData 有數據,那么就把imageData 生成image 根據響應的option 對image進行操作

看到這里沒看見緩存,那么繼續找緩存在哪里。

其實在上面分析的block中已經完成緩存分析了。只有由于跳轉太多,并且不是嚴格按照請求image 來分析的,所以看著有點亂,無頭無需的。沒關系。學習么?那那么容易一下看懂。那我們下篇就先分析分析我們請求過程中使用的每個類的作用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容