【iOS】SDWebImage源码学习--未完

SDWebImage的主要功能及相关知识点

SDWebImage是一个流行的第三方库,用于在iOS和macOS应用程序中异步下载和缓存图像。它提供了一种简单而强大的方式来处理网络图像加载和缓存,具有以下主要功能:

  1. 异步下载:SDWebImage使用多线程机制,允许在后台异步下载图像,以避免阻塞应用程序的用户界面。
  2. 图像缓存:它具有内存缓存和磁盘缓存机制,可以自动将下载的图像保存在内存和磁盘中。这样,在后续的加载中,它可以快速从缓存中获取图像,而不必再次下载。
  3. 占位图和渐进式加载:SDWebImage支持在图像下载期间显示占位图,以及渐进式加载图像,使用户可以逐步看到图像的加载进度。
  4. 缓存清理:SDWebImage还提供了清理缓存的选项,可以根据需要手动清理过期或不再需要的缓存。

工具类及其功能

  • NSData+ImageContentType 通过Image data判断当前图片的格式
  • SDImageCache 缓存 定义了 Disk 和 memory二级缓存(NSCache)负责管理cache 单例
  • SDWebImageCompat 保证不同平台/版本/屏幕等兼容性的宏定义和内联 图片缩放
  • SDWebImageDecoder 图片解压缩,内部只有一个接口
  • SDWebImageDownloader 异步图片下载管理,管理下载队列,管理operation 管理网络请求 处理结果和异常 单例
    存放网络请求回调的block 自己理解的数据结构大概是
    // 结构{“url”:[{“progress”:“progressBlock”},{“complete”:“completeBlock”}]}
  • SDWebImageDownloaderOperation 实现了异步下载图片的NSOperation,网络请求给予NSURLSession 代理下载
    自定义的Operation任务对象,需要手动实现start cancel等方法
  • SDWebImageManager 核心管理类 主要对缓存管理 + 下载管理进行了封装 主要接口downloadImageWithURL单利
  • SDWebImageOperation operation协议 只定义了cancel operation这一接口 上面的downloaderOperation的代理
  • SDWebImagePrefetcher 低优先级情况下预先下载图片,对SDWebImageViewManager进行简单封装 很少用
  • MKAnnotationView+WebCache – 为MKAnnotationView异步加载图片
  • UIButton+WebCache 为UIButton异步加载图片
  • UIImage+GIF 将Image data转换成指定格式图片
  • UIImage+MultiFormat 将image data转换成指定格式图片
  • UIImageView+HighlightedWebCache 为UIImageView异步加载图片
  • UIImageView+WebCache 为UIImageView异步加载图片
  • UIView+WebCacheOperation 保存当前MKAnnotationView / UIButton / UIImageView异步下载图片的operations

下载流程

基本使用流程

在这里插入图片描述

实现流程

  1. SDWebImage首先会检查所请求的图片是否存在缓存中(包括内存缓存和磁盘缓存)。如果图片在缓存中找到,将立即从缓存中加载,以提供更快的访问速度。
  2. 如果在缓存中未找到图片,则SDWebImage会启动一个异步下载任务,从提供的URL下载图片。它会在后台处理网络请求和图片下载,而不会阻塞用户界面。
  3. 在图片下载期间,SDWebImage可以显示指定的占位图像(如果提供了)在UIImageView中。
  4. 图片下载完成后,SDWebImage会将其加载到UIImageView中,并自动处理缓存,以便在将来的请求中能够快速获取图片。

源码解析

我们根据调用流程一步一步来。

调用1

调用UIImageView+WebCache中的sd_setImageWithURL系列方法:

- (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 {
   
    [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);
                               }
                           }];
}

调用2

上面的全能方法,实际上调用了UIView+WebCache类的一个方法:

- (nullable id<SDWebImageOperation>)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;
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    
    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];
    }
    
    BOOL shouldUseWeakCache = NO;
    if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
   
        shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
    }
    if (!(options & SDWebImageDelayPlaceholder)) {
   
        if (shouldUseWeakCache) {
   
            NSString *key = [manager cacheKeyForURL:url context:context];
            // call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
            // this unfortunately will cause twice memory cache query, but it's fast enough
            // in the future the weak cache feature may be re-design or removed
            [((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
        }
        dispatch_main_async_safe(^{
   
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    
    id <SDWebImageOperation> operation = nil;
    
    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
        
        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);
        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 callCompletedBlockClosure = ^{
   
                if (!self) {
    return; }
                if (!shouldNotSetImage) {
   
                    [self sd_setNeedsLayout];
                }
                if (completedBlock && shouldCallCompletedBlock) {
   
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {
   
                dispatch_main_async_safe(callCompletedBlockClosure);
                return;
            }
            
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
   
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
   
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                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) {
   
                    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
                callCompletedBlockClosure();
            });
        }];
        [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);
            }
        });
    }
    
    return operation;
}

首先是方法名:

其总共有五个参数,URL就是我们需要下载的在线图片链接,placeholder(占位符)Image其是UIImage类型,而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,
    
    /**
     * 处理存储在NSHTTPCookieStore中的cookie
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     */
    SDWebImageHandleCookies = 1 << 5,
    
    /**
     * 启用允许不受信任的SSL证书。
	 * 用于测试目的。在生产中请谨慎使用。
     */
    SDWebImageAllowInvalidSSLCertificates = 1 << 6,
    
    /**
     * 默认情况下,图像按照它们排队的顺序加载。这个标志把他们推在队伍的前面。
     */
    SDWebImageHighPriority = 1 << 7,
    
    /**
     * 默认情况下,在加载图像时加载占位符图像。此标志将延迟加载占位符图像,直到图像完成加载。
	 * @注:这用于将占位符视为**错误占位符**,而不是默认的**加载占位符**。如果图像加载被取消或出现错误,占位符将始终被设置。
	 * 因此,如果你想**错误占位符**和**加载占位符**存在,使用' SDWebImageAvoidAutoSetImage '手动设置两个占位符和最终加载的图像由你的手取决于加载结果。
     */
    SDWebImageDelayPlaceholder = 1 << 8,
    
    /**
     * 我们通常不会在动画图像上应用变换,因为大多数Transform无法管理动画图像。
	 * 无论如何的药变换,使用此标志来转换它们。
     */
    SDWebImageTransformAnimatedImage = 1 << 9,
    
    /**
     * 默认情况下,图片下载后会添加到imageView中。但在某些情况下,我们想要
	 * 在设置图像之前先设置一下(例如应用滤镜或添加交叉渐变动画)
	 * 使用此标志,如果你想手动设置图像在完成时成功
     */
    SDWebImageAvoidAutoSetImage = 1 << 10,
/**
     * 默认情况下,根据图像的原始大小对其进行解码。
	 * 此标志将缩小图像到与设备受限内存兼容的大小。
	 * 要控制内存限制,请检查' SDImageCoderHelper.defaultScaleDownLimitBytes ' (iOS上默认为60MB)
	 * 这将实际转化为使用上下文选项'。imageThumbnailPixelSize '从v5.5.0(在iOS上默认为(3966,3966))。以前没有。
	 * 从v5.5.0开始,这个标志也会影响渐进式和动画图像。以前没有。
	   如果你需要细节控件,最好使用上下文选项' imageThumbnailPixelSize '和' imagePreserveAspectRatio '代替。
     */
    SDWebImageScaleDownLargeImages = 1 << 11,
    
    /**
     * 默认情况下,当图像已经缓存在内存中时,我们不会查询图像数据。此掩码可以强制同时查询图像数据。然而,这个查询是异步的,除非你指定' SDWebImageQueryMemoryDataSync '
     */
    SDWebImageQueryMemoryData = 1 << 12,
    
    /**
     * 默认情况下,当您只指定' SDWebImageQueryMemoryData '时,我们将异步查询内存图像数据。并结合此掩码同步查询内存图像数据。
	 * @note不建议同步查询数据,除非你想确保在同一个运行循环中加载图像,以避免在单元重用期间闪烁。
     */
    SDWebImageQueryMemoryDataSync = 1 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值