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 来分析的,所以看着有点乱,无头无需的。没关系。学习么?那那么容易一下看懂。那我们下篇就先分析分析我们请求过程中使用的每个类的作用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容