[iOS开发]SDWebImage源码学习

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]SDWebImage源码学习相关的知识,希望对你有一定的参考价值。

SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能。它具有以下功能:

  1. 一个异步的图片加载器。
  2. 一个异步的内存+磁盘图片缓存
  3. 支持GIF、WebP图片
  4. 后台图片解压缩处理
  5. 确保同一个URL的图片不被多次下载
  6. 确保非法的URL不会被反复加载
  7. 确保下载及缓存时,主线程不被阻塞。

大致流程如下:

调用

接下来一点点看,我们一般都会使用方法加载图片:

//UIImage+WebCache.h
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                 completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;

UIImage+WebCache.h文件里,我们还可以找到常见的:

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

等常用方法。
对于SDExternalCompletionBlock,我们可以在SDWebImageManager.h中找到定义:

typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);

查看UIImage+WebCache.m文件,可以看到这些方法的实现代码:

- (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 placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context 
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context 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 SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock 
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock];

它们最终都调用了

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

方法。查看它的代码:

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock 
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                             context:context
                       setImageBlock:nil
                            progress:progressBlock
                           completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) 
                               if (completedBlock) 
                                   completedBlock(image, error, cacheType, imageURL);
                               
                           ];

发现它调用了

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock;

方法。其简要流程为:

  1. SDWebImageContext 复制并转换为 immutable,获取其中的 validOperationKey 值作为校验 id,默认值为当前 view 的类名;
  2. 执行 sd_cancelImageLoadOperationWithKey 取消上一次任务,保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突;
  3. 设置占位图;
  4. 初始化 SDWebImageManagerSDImageLoaderProgressBlock , 重置 NSProgressSDWebImageIndicator;
  5. 开启下载loadImageWithURL: 并将返回的 SDWebImageOperation 存入 sd_operationDictionarykeyvalidOperationKey;
  6. 取到图片后,调用 sd_setImage: 同时为新的 image 添加 Transition 过渡动画;
  7. 动画结束后停止 indicator

UIView+Cache.h文件中可以找到它的实现:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock 
    if (context) 
        // copy to avoid mutable object
        // 复制以避免可变对象
        context = [context copy];
     else 
        context = [NSDictionary dictionary];
    
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) 
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        // 通过操作键传递到下游,可用于跟踪操作或图像视图类
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    
    self.sd_latestOperationKey = validOperationKey;
    //1.取消先前的下载任务
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    
    if (!(options & SDWebImageDelayPlaceholder)) 
        dispatch_main_async_safe(^
        	//设置占位图
        	// 作为图片下载完成之前的替代图片。
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        );
    
    
    if (url) 
        // reset the progress
        // 重置进度
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        // 获取图像加载进度
        if (imageProgress) 
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        
        
#if SD_UIKIT || SD_MAC
        // check and start image indicator
        // 是否显示进度条
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
		// 获取图像管理者对象
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];
        // 如果自定义了图像管理者对象就用自定义的,否则就用单例对象
        if (!manager) 
            manager = [SDWebImageManager sharedManager];
         else 
            // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
            // 删除此管理器以避免保留周期(管理器 -> 加载程序 -> 操作 -> 上下文 -> 管理器)
            SDWebImageMutableContext *mutableContext = [context mutableCopy];
            mutableContext[SDWebImageContextCustomManager] = nil;
            context = [mutableContext copy];
        
        
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) 
            if (imageProgress) 
                imageProgress.totalUnitCount = expectedSize;
                imageProgress.completedUnitCount = receivedSize;
            
#if SD_UIKIT || SD_MAC
            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) 
                double progress = 0;
                if (expectedSize != 0) 
                    progress = (double)receivedSize / expectedSize;
                
                progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                dispatch_async(dispatch_get_main_queue(), ^
                    [imageIndicator updateIndicatorProgress:progress];
                );
            
#endif
            if (progressBlock) 
                progressBlock(receivedSize, expectedSize, targetURL);
            
        ;
        @weakify(self);
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) 
            @strongify(self);
            if (!self)  return; 
            // if the progress not been updated, mark it to complete state
            // 如果进度未更新,请将其标记为完成状态
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) 
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
            
            
#if SD_UIKIT || SD_MAC
            // check and stop image indicator
            // 检查和停止图像指示器
            if (finished) 
                [self sd_stopImageIndicator];
            
#endif
            
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^
            	//self是否被释放
                if (!self)  return; 
                if (!shouldNotSetImage) 
                    [self sd_setNeedsLayout];
                
                //不要自动设置图片,调用Block传入UIImage对象
                if (completedBlock && shouldCallCompletedBlock) 
                    completedBlock(image, data, error, cacheType, finished, url);
                
            ;
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // 我们得到了一个图像,但SDWebImageAvoidAutoSetImage标志被设置了
            // OR 或
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            // 我们没有图像,也没有设置SDWebImageDelayPlaceholder
            if (shouldNotSetImage) 
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            
            
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) 
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                // 我们得到了一个图像,但SDWebImageAvoidAutoSetImage没有设置
                targetImage = image;
                targetData = data;
             else if (options & SDWebImageDelayPlaceholder) 
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                // 我们没有图像,并且设置了SDWebImageDelayPlaceholder标志
                targetImage = placeholder;
                targetData = nil;
            
            
#if SD_UIKIT || SD_MAC
            // check whether we should use the image transition
            // 检查我们是否应该使用图像过渡
            SDWebImageTransition *transition = nil;
            BOOL shouldUseTransition = NO;
            if (options & SDWebImageForceTransition) 
                // Always
                shouldUseTransition = YES;
             else if (cacheType == SDImageCacheTypeNone) 
                // From network
                shouldUseTransition = YES;
             else 
                // From disk (and, user don't use sync query)
                if (cacheType == SDImageCacheTypeMemory) 
                    shouldUseTransition = NO;
                 else if (cacheType == SDImageCacheTypeDisk) 
                	// SDWebImageQueryMemoryDataSync:
                    // 默认情况下,当您仅指定“SDWebImageQueryMemoryData”时,我们会异步查询内存映像数据。
                    // 将此掩码也组合在一起,以同步查询内存图像数据
                    // 不建议同步查询数据,除非您要确保在同一 runloop 中加载映像以避免在单元重用期间闪烁。
                    // SDWebImageQueryDiskDataSync:
                    // 默认情况下,当内存缓存未命中时,我们会异步查询磁盘缓存。此掩码可以强制同步查询磁盘缓存(当内存缓存未命中时)。
                    // 这两个选项打开则NO。
                    if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) 
                        shouldUseTransition = NO;
                     else 
                        shouldUseTransition = YES;
                    
                 else 
                    // Not valid cache type, fallback
                    shouldUseTransition = NO;
                
            
            if (finished && shouldUseTransition) 
                transition = self.sd_imageTransition;
            
#endif
            dispatch_main_async_safe(^
#if SD_UIKIT || SD_MAC
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
                callCompletedBlockClojure();
            );
        ];
        // 将生成的加载操作赋给UIView的自定义属性,并赋值
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
     else 
#if SD_UIKIT || SD_MAC
        [self sd_stopImageIndicator];
#endif
        dispatch_main_async_safe(^
            if (completedBlock) 
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@NSLocalizedDescriptionKey : @"Image url is nil"];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            
        );
    

参数列表中,有一项SDWebImageOptions,在SDWebImageDefine.h里面,定义了SDWebImageOptions,其是一种暴露在外的可供使用者使用的选择方法。

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) 
    // 默认情况下,当URL下载失败时,URL会被列入黑名单,导致库不会再去重试,该标记用于禁用黑名单
    SDWebImageRetryFailed = 1 << 0,
    // 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时
    SDWebImageLowPriority = 1 << 1,
    // 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。
    // 默认情况下,图像在下载完成后一次性显示
    SDWebImageProgressiveLoad = 1 << 2,
    // 即使缓存了映像,也要遵循 HTTP 响应缓存控件,并在需要时从远程位置刷新映像。
    // 磁盘缓存将由 NSURLCache 而不是 SDWebImage 处理,从而导致性能略有下降。
    // 此选项有助于处理同一请求URL后面的图像变化,例如Facebook图形API个人资料图片。
    // 如果刷新缓存的图像,则对缓存的图像调用一次完成块,然后对最终图像调用一次完成块。
    // 仅当无法使用嵌入式缓存破坏参数使 URL 保持静态时,才使用此标志。
    SDWebImageRefreshCached = 1 << 3,
    // 在ios 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成
    // 如果后台任务超时,则操作被取消
    SDWebImageContinueInBackground = 1 << 4,
    // 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理存储在NSHTTPCookieStore中的cookie
    SDWebImageHandleCookies = 1 << 5,
    // 允许不受信任的SSL认证
    // 用于测试目的。在生产中谨慎使用。
    SDWebImageAllowInvalidSSLCertificates = 1 << 6以上是关于[iOS开发]SDWebImage源码学习的主要内容,如果未能解决你的问题,请参考以下文章

SDWebImage源码阅读SDImageCacheConfig/SDImageCache(下)

SDWebImage源码分析

SDWebImage源码阅读NSImage+WebCache

通读SDWebImage源码

iOS网络——SDWebImage SDImageDownloader源码解析

iOS网络——SDWebImage SDImageDownloader源码解析