SDWebImage 源码解析

本文基于SDWebImage 4.0分析

SDWebImage 是一个支持异步下载加二级缓存的UIImageView 的扩展框架,主要功能如下:

  • 扩展UIImageView,UIButton,MKAnnotationView,增加网络图片与缓存管理。
  • 一个异步的图片加载器
  • 一个异步的内存+磁盘图片缓存,拥有自动的缓存过期处理机制。
  • 支持后台图片解压缩处理
  • 确保同一个URL的图片不被多次下载,确保虚假的URL不会被反复加载
  • 确保下载及缓存时,主线程不被阻塞
  • 使用GCD,NSOperation&NSOperationQueue与ARC

看下SDWebImage 的 功能框架图:

SDWebImage 源码解析_第1张图片
1.png
  • UIImageView + WebCache 和其他的扩展都是用户交互直接打交道的。
  • SDWebImageManager是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁。
  • SDImageCache负责图片的缓存,包括内存缓存和磁盘缓存
  • SDWebImageDownloader负责维持图片的下载队列;
  • SDWebImageDownloaderOperation负责真正的图片下载请求;
  • SDWebImageDecoder负责图片的解压缩;
  • SDWebImagePrefetcher负责图片的预取;

看下总的模块图:

SDWebImage 源码解析_第2张图片
图片来自网络.png

下面按照图片等加载流程来分析:

1.以 UIImageView+WebCache 扩展为例,其他扩展一样的:
- (void)sd_setImageWithURL:(nullable NSURL *)url {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}

//公共访问接口,根据用户的需求传入不同的参数
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil  //没有设置operationkey
                       setImageBlock:nil //这里传了空
                            progress:progressBlock
                           completed:completedBlock];
}

上面这一大段代码是:UIImageView 的下载一个图片对外的接口,用户可以根据自己的需求设置不同的操作,包括图片url,占位图,下载的options,下载进度回调,下载完成回调。

2.在看UIView 中的接口:
- (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 {
    
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); //检测操作key为空显示为该类名
    
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];//下载之前先根据操作key取消之前下载队列中的任务/
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//再将对应的imageurl 存起来
    
     // SDWebImageDelayPlaceholder枚举值的含义是取消网络图片加载好前展示占位图片。
//     所以在这里并不能直接把placeholder直接赋值给self.image,而要用if条件排除这种情况。
    //就是说不是SDWebImageDelayPlaceholder这种情况的进入设置占位图片
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    if (url) { //如果图片url 不为空
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        
        //开始执行下载操作。然后回调下载的数据, 下面SDWebImageManager这个类很重要
        __weak __typeof(self)wself = self;
        id  operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            [sself sd_removeActivityIndicator];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{        //dispatch_main_sync_safe : 保证block能在主线程进行 ,更新UI
                if (!sself) {
                    return;
                }
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { //设置不自动设置图片,一般这个条件是满足不了的。。
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {//如果图片下载回调成功,self 设置图片,先设置完成下面再回调。。。高明之处。
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {//若果没下载到数据,继续设置占位图片
                    if ((options & SDWebImageDelayPlaceholder)) {//除了这种条件 其他都不满足。。
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {//若果完成,继续回调到imageview层
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        
        
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else { //如果url 是空的操作。。。停止菊花
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) { // 向上层回调一个url 错误的异常
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

上面操作涉及的UIView+WebCacheOperation

/*
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey 
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

#import "UIView+WebCacheOperation.h"

#if SD_UIKIT || SD_MAC

#import "objc/runtime.h"

static char loadOperationKey;

typedef NSMutableDictionary SDOperationsDictionary;

@implementation UIView (WebCacheOperation)

//操作字典
- (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;
}
//把当前操作以及对应的key 存入字典中
- (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 (id  operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}
//  仅仅根据 key 移除对应的当前 UIView 的操作,并没有取消它们。
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        SDOperationsDictionary *operationDictionary = [self operationDictionary];
        [operationDictionary removeObjectForKey:key];
    }
}

@end

#endif

3.按照上面的步骤下一步我们该分析一下SDWebImageManager这个集缓存和下载与一体的管理类了。
  • 根据不同的操作下载不同的处理
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    // 默认情况下,当URL下载失败时,URL会被列入黑名单,导致库不会再去重试,该标记用于禁用黑名单
    SDWebImageRetryFailed = 1 << 0,
    // 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时
    SDWebImageLowPriority = 1 << 1,
    // 该标记禁用磁盘缓存
    SDWebImageCacheMemoryOnly = 1 << 2,
    // 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。
    // 默认情况下,图像在下载完成后一次性显示
    SDWebImageProgressiveDownload = 1 << 3,
    // 即使图片缓存了,也期望HTTP响应cache control,并在需要的情况下从远程刷新图片。
    // 磁盘缓存将被NSURLCache处理而不是SDWebImage,因为SDWebImage会导致轻微的性能下载。
    // 该标记帮助处理在相同请求URL后面改变的图片。如果缓存图片被刷新,则完成block会使用缓存图片调用一次
    // 然后再用最终图片调用一次
    SDWebImageRefreshCached = 1 << 4,
    // 在iOS 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成
    // 如果后台任务超时,则操作被取消
    SDWebImageContinueInBackground = 1 << 5,
    // 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理存储在NSHTTPCookieStore中的cookie
    SDWebImageHandleCookies = 1 << 6,
    // 允许不受信任的SSL认证
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    // 默认情况下,图片下载按入队的顺序来执行。该标记将其移到队列的前面,
    // 以便图片能立即下载而不是等到当前队列被加载
    SDWebImageHighPriority = 1 << 8, //256
    // 默认情况下,占位图片在加载图片的同时被加载。该标记延迟占位图片的加载直到图片已以被加载完成
    SDWebImageDelayPlaceholder = 1 << 9, //512
    // 通常我们不调用动画图片的transformDownloadedImage代理方法,因为大多数转换代码可以管理它。
    // 使用这个票房则不任何情况下都进行转换。
    SDWebImageTransformAnimatedImage = 1 << 10,   //1024
    // 下载完成后避免自动设置图片到view上,用户自己去操作。
    SDWebImageAvoidAutoSetImage = 1 << 11,
     //缩小图片比例,默认不缩小的
       SDWebImageScaleDownLargeImages = 1 << 12
};

这个类的核心方法:


- (id )loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // 使用断言来保证完成的 Block 不能为空,也就是说如果你不需要完成回调,直接使用 SDWebImagePrefetcher 就行
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // 保证 URL 是 NSString 类型,转换成 NSURL 类型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    // 保证 url 为 NSURL 类型
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    
    
    //这里有出现了个operation操作,自定义的
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    

    // 判断是否为下载失败的 url
    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            // 保证线程安全
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    // 如果是失败的 url 且 operations 不为 SDWebImageRetryFailed,或者 url 为空直接返回错误
   // 完整的url字符串 取消下载直接return;
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    //把对应的操作加入到运行操作数组中
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    // 获取 url 对应的 Key
    NSString *key = [self cacheKeyForURL:url];
    
    
    
    //1. 从 Cache 中获取图片,它结合 option,进行不同的操作
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        
        //如果 Operation 已经取消,则移除,并结束程序的执行
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        
        //(没有缓存图片) || (即使有缓存图片,也需要更新缓存图片)(可能有缓存存在。。。)   || (代理没有响应imageManager:shouldDownloadImageForURL:消息,默认返回yes,需要下载图片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下载图片)
      //  &&&&&&&&&&&&&&&&&&
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            
            //1. 存在缓存图片 && 即使有缓存图片也要下载更新图片
            if (cachedImage && options & SDWebImageRefreshCached) {
                //如果缓存中找到了图像,但提供了SDWebImageRefreshCached,请通知有关缓存的图像
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            
            // 继续执行。。。。。
            
            //            SDWebImageDownloaderOptions 根据不同的选项做不同的操作,
            //            根据 SDWebImageOptions 转换成对应的 SDWebImageDownloaderOptions。
            //            这里需要注意位运算,根据位运算可以计算出不同的选项。
            //            那么使用位定义的枚举和用普通定义的枚举值有什么优缺点?需要读者考虑。比如下面这两种定义方法个的优缺点。
            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) {
                //强制渐进关闭,如果图像已经缓存但强制刷新
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                //忽略从NSURLCache读取的图像,如果缓存的图像,但强制刷新
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            
            /**********************************************************************************************************************/
            //**************下面是不存在缓存图片时候开始下载**************//
            //            使用 imageDownloader 下载图片,下载完成后保存到缓存,并移除 Operation。
            //            如果发生错误,,需要将失败的 Url 保存到 failedURLs,避免实效的 Url 多次下载。
            //            这里需要注意一个 delegate ([self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]),
            //            它需要调用者自己实现,这样缓存中将保存转换后的图片。
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    
                    if (error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) { //这个是没法下载
                        // 在错误url名单中添加当前的url
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }else {//这个是另一个情况下载成功了
                    // 如果需要下载失败后重新下载,则将当前url从失败url名单里移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); //是否进行硬盘缓存
                    
                    
                    //(即使缓存存在,也要刷新图片) && 缓存图片 && 不存在下载后的图片:不做操作
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        
                        //(下载图片成功 && (没有动图||处理动图) && (下载之后,缓存之前  先处理图片) SDWebImageManagerDelegate 这个协议
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            
                            //转换后的图片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            
                            if (transformedImage && finished) {
                                //判断是否转换成功
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                //缓存图片
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                        
                        
                    } else {
                        //(图片下载成功并结束)
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                       // 回调图片
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                //如果完成,从当前运行的操作列表里移除当前操作
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            /**********************************************************************************************************************/
            
            
            //            取消的block
            operation.cancelBlock = ^{
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self safelyRemoveOperationFromRunning:strongOperation];
            };//下面这一块是接上面的判断 的&&&&&&&&&&&&&&&&&&,判断有缓存的图片时候
        } 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];
        } else {
            //没有缓存的图片,而且下载被代理终止了
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            // 调用完成的block
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
        
    }];
    return operation;
}

其中有个类:SDWebImageCombinedOperation

@interface SDWebImageCombinedOperation : NSObject 

@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end

实现了这个抽象接口:

#import 

@protocol SDWebImageOperation 

- (void)cancel;

@end

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();
           _cancelBlock = nil;
    }
}

缓存操作相关:

//通过key 查询缓存值
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
//   key 为空直接回调空
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // 第一步检查内存中的缓存
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        //因为图片有缓存可供使用,所以不用实例化NSOperation,直接范围nil
        return nil;
    }
    //================查看磁盘的缓存=================//
    NSOperation *operation = [NSOperation new];
    //异步操作
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // 在用之前就判断operation是否被取消了,作者考虑的非常严谨
            return;
        }

        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                // cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //存入内存缓存中
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

下载操作相关:

//该方法主要操作在于创建一个SDWebImageDownloaderOperation 对象,然后对该进行属性设置,再将该对象加入名字为downloadQueue 的队列。
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    
    __weak SDWebImageDownloader *wself = self;

    // block 返回值是 SDWebImageDownloaderOperation,在 block 中创建一个 SDWebImageDownloaderOperation
    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        __strong __typeof (wself) sself = wself;
        
        NSTimeInterval timeoutInterval = sself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

        //为了防止潜在的重复缓存(NSURLCache + SDImageCache),我们禁用缓存的图像请求,如果另有说明
        
//        @method initWithURL:
//        @abstract使用给定的URL初始化一个NSURLRequest
//        缓存策略。
//        @discussion这是指定的初始化程序
//        NSURLRequest类。
//        @param URL请求的URL。
//        @param cachePolicy请求的缓存策略。
//        @param timeoutInterval请求的超时间隔。见
//        有关更多信息的 timeoutInterval 的评论
//        超时间隔。
//        @result初始化的NSURLRequest。
        //以这种方式生成一个request
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) 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;
        }
        /**********************上面是配置一个request****************************/
        
        
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        
        
        // 处理 HTTP 认证的,大多情况不用处理
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        // 设置 Operation 的优先级
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
       
        //加入队列中开始下载了。。。
        [sself.downloadQueue addOperation:operation];
        
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
      //通过系统地添加新的操作作为最后一个操作的依赖关系来模拟LIFO执行顺序
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation; //返回给自己用........
    }];
}

初始化相关操作:

//有三个方法用来创建NSURLSessionConfiguration:
//
//defaultSessionConfiguration- 使用全局的cache,cookie和credential storage objects来创建configuration对象。
//
//ephemeralSessionConfiguration – 这个configuration用于“private” sessions,还有对于cache, cookie, or credential storage objects的非永久存储。
//
//backgroundSessionConfiguration – 做远程push通知或是应用程序挂起的时候就要用到这个configuration。
//初始化一些配置,包括操作类,最大队列数,超时时间,还有一个NSURLSession 并设置了 代理
//在SDWebImageDownloader的初始化方法中有创建了一个NSURLSession,这个对象是用来执行图片下载的网络请求的,它的代理对象是SDWebImageDownloader。所以我们在SDWebImageDownloader类里面找到对应的NSURLSession的代理方法,那么就可以看到下载操作的一些细节。

- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES;
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;

        sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;

        /**
         *为此任务创建会话
         *作为代理队列发送nil,以便会话创建一个用于执行所有委托的串行操作队列
         *方法调用和完成处理程序调用。
         */
        self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                     delegate:self
                                                delegateQueue:nil];
    }
    return self;
}

下面是当前类的session 的代理方法:

#pragma mark NSURLSessionDataDelegate
// 1.接收到服务器的响应

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

    NSLog(@"xxxxxxxxxxxxxxx11111111111");
    
    // 取得 SDWebImageDownloaderOperation 对象将URLSession的回调转发给它
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
    [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}

#pragma mark NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // Identify the operation that runs this task and pass it the delegate method
    NSLog(@"uuuuuuuuuuuuuuuuuu11111111111");
    // 取得 SDWebImageDownloaderOperation 对象将URLSession的回调转发给它
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
    [dataOperation URLSession:session task:task didCompleteWithError:error];
}
...... 省略部分
......

dataOperation 继承抽象接口的方法。

核心功能SDWebImageDownloaderOperation:
继承自NSOperation:

- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

#if SD_UIKIT
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        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 (!self.unownedSession) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }
    
    [self.dataTask resume];

    if (self.dataTask) {
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    } else {
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
    }

#if SD_UIKIT
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

下面是收到数据的回调:

#pragma mark NSURLSessionDataDelegate
//收到响应开始,这个是sdwebimageDownloader 中的代理方法直接调用过来的
//收到网络请求的响应之后判断statusCode,若是正常的statusCode,
//那么就根据数据量创建对应大小的NSMutableData,发送对应的通知。若是出现异常,那么就做好对应的错误回调和发送对应的错误通知。
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    
    NSLog(@"yyyyyyyyyyyyyyyy2222222222222");
    
    //'304 Not Modified' is an exceptional one
    // (没有statusCode) 或者 (statusCode小于400 并且 statusCode 不等于304)
    // 若是请求响应成功,statusCode是200,那么会进入这个代码分支
    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;
        //下载过程回调,收到数据量为0
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, expected, self.request.URL);
        }
        //根据期望收到的数据量创建NSMutableData,该NSMutableData用户保存收到的数据量
        self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
        //保存响应对象
        self.response = response;
        //发送网络请求收到响应的通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
        });
    }
    else {
        NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
        
        //This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
        //In case of 304 we need just cancel the operation and return cached image from the cache.
        if (code == 304) {
            //code 304 取消下载 直接返回缓存中的内容

            [self cancelInternal];
        } else {
            //数据请求任务取消

            [self.dataTask cancel];
        }
        //发送停止下载的通知

        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });
        // 错误处理回调
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];

        [self done];
    }
    // 调用completionHandler回调
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}

下载完成的回调:
#pragma mark NSURLSessionTaskDelegate
//数据请求任务完成的代理方法
//图片数据下载完成之后,最重要的一件事就是将图片数据转成UIImage对象。
//然后通过 - (void)callCompletionBlocksWithImage:imageData:error:finished: 方法
//将该UIImage对象和图片数据的data 传给SDWebImageDownloader。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"wwwwwwwwwww2222222222222");

    @synchronized(self) {
        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) {
        //错误回调
        [self callCompletionBlocksWithError:error];
    } else {
        if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
            /**
             *  See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
             *  Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
             *    and images for which responseFromCached is YES (only the ones that cannot be cached).
             *  Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
             */
            if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
                // hack
                [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
            } else if (self.imageData) {
                //处理图片方向和图片格式
                UIImage *image = [UIImage sd_imageWithData:self.imageData];
                // 获取缓存key
                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];
}

至此一个图片下载完成

最近心情不好,抽空再把细节介绍清楚。

友情链接:
http://www.jianshu.com/p/13e0755c1184

你可能感兴趣的:(SDWebImage 源码解析)