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

news2024/11/25 4:28:59

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 << 13,
    /**
     * 默认情况下,当内存缓存丢失时,我们异步查询磁盘缓存。此掩码可以强制同步查询磁盘缓存(当内存缓存丢失时)。
	 * @注这3个查询选项可以组合在一起。有关这些掩码组合的完整列表,请参阅wiki页面。
	 * @note不建议同步查询数据,除非你想确保在同一个运行循环中加载图像,以避免在单元重用期间闪烁。
     */
    SDWebImageQueryDiskDataSync = 1 << 14,
    
    /**
     * 默认情况下,当缓存丢失时,将从加载器加载图像。这个标志可以防止只从缓存加载。
     */
    SDWebImageFromCacheOnly = 1 << 15,
    
    /**
     * 默认情况下,我们在从加载器加载图像之前查询缓存。这个标志可以防止只从加载器加载。
     */
    SDWebImageFromLoaderOnly = 1 << 16,
    
    /**
     * 默认情况下,当你使用' SDWebImageTransition '在图片加载完成后做一些视图转换时,这个转换只适用于来自管理器的回调是异步的(来自网络,或磁盘缓存查询)。
	 * 这个掩码可以强制在任何情况下应用视图转换,如内存缓存查询,或同步磁盘缓存查询。
     */
    SDWebImageForceTransition = 1 << 17,
    /**
     * 默认情况下,我们将在缓存查询时在后台解码图像,然后从网络下载。这有助于提高性能,因为在屏幕上渲染图像时,需要首先对图像进行解码。但这是Core Animation在主队列上发生的。
	  	然而,这个过程也可能增加内存的使用。如果由于内存消耗过多而遇到问题,此标志可以阻止解码图像。
     */
    SDWebImageAvoidDecodeImage = 1 << 18,
    
    /**
     * 默认情况下,我们解码动画图像。这个标志可以强制解码第一帧,并产生静态图像。
     */
    SDWebImageDecodeFirstFrameOnly = 1 << 19,
    
    /**
     * 默认情况下,对于' SDAnimatedImage ',我们在渲染期间解码动画图像帧以减少内存使用。但是,当动画图像被许多imageview共享时,您可以指定将所有帧预加载到内存中以减少CPU使用。
	 * 这将在后台队列中触发' preloadAllAnimatedImageFrames '(仅限磁盘缓存和下载)。
     */
    SDWebImagePreloadAllFrames = 1 << 20,
    
    /**
     * 默认情况下,当你使用' SDWebImageContextAnimatedImageClass '上下文选项(如使用' SDAnimatedImageView '设计		使用' SDAnimatedImage '),我们可能仍然使用' UIImage '当内存缓存hit,或图像解码器是不可用的产生一个完全匹配你的自定义类作为后备解决方案。
	 * 使用此选项,可以确保我们总是回调图像与您提供的类。如果未能产生一个,一个错误代码' SDWebImageErrorBadImageData '将被使用。
	 * 注意这个选项不兼容' SDWebImageDecodeFirstFrameOnly ',它总是产生一个UIImage/NSImage。
     */
    SDWebImageMatchAnimatedImageClass = 1 << 21,
    
    /**
     * 默认情况下,当我们从网络加载图像时,图像将被写入缓存(内存和磁盘,由' storeCacheType '上下文选项控制)。
	 * 这可能是一个异步操作,最终的' SDInternalCompletionBlock '回调不能保证磁盘缓存写入完成,可能导致逻辑错误。(例如,您在完成块中修改了磁盘数据,但是磁盘缓存还没有准备好)
	 * 如果你需要在完成块中处理磁盘缓存,你应该使用这个选项来确保回调时磁盘缓存已经被写入。
	 * 注意,如果您在使用自定义缓存序列化器或使用转换器时使用此功能,我们也将等待输出图像数据写入完成。
     */
    SDWebImageWaitStoreCache = 1 << 22,
    
    /**
     * 我们通常不会在矢量图像上应用变换,因为矢量图像支持动态更改为任何大小,栅格化到固定大小会丢失细节。要修改矢量图像,可以在运行时处理矢量数据(例如修改PDF标记/ SVG元素)。
	 * 无论如何都要在矢量图片上应用变换,使用此标志来转换它们。
     */
    SDWebImageTransformVectorImage = 1 << 23
};

1. context

if (context) {
        // copy to avoid mutable object
        // 避免可变对象
        context = [context copy];
    } else {
        context = [NSDictionary dictionary];
    }
    // 利用context获取validOperationKey
    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;

对于

[self sd_cancelImageLoadOperationWithKey:validOperationKey];

其源码:

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        // 从队列中取消正在进行的加载
        SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
        id<SDWebImageOperation> operation;
        
        @synchronized (self) {
            operation = [operationDictionary objectForKey:key];
        }
        if (operation) {
            if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                [operation cancel];
            }
            @synchronized (self) {
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
}

这段代码在UIView+WebCacheOperation中,这个类主要负责对下载operation的操作。它使用关联对象针对每个UIKit对象在内存中维护一个字典operationDictionary。可以对不同的key值添加对应的下载operation,也可以在下载操作没有完成的时候根据key取到operation进行取消。

operationDictionary的key一般是类名。如此同一个UIImageView同时调用两次,第一次的下载操作会先被取消,然后将operationDictionary的中的operation对应到第二次的下载操作。

然后我们来看看operationDictionary的源码:

- (SDOperationsDictionary *)sd_operationDictionary {
    @synchronized(self) {
        SDOperationsDictionary *operations = objc_getAssociatedObject(self, @selector(sd_operationDictionary));
        if (operations) {
            return operations;
        }
        operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
        objc_setAssociatedObject(self, @selector(sd_operationDictionary), operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return operations;
    }
}

通过键值返回一个关联对象。如果operations为空,那么就创建一个对象,与键值绑定。

如果有两个相同的键值进入,得到的关联对象也是一样的。传入key在返回的字典中查找是否已经存在,如果存在则取消所有操作,conformsToProtocol方法如果符合这个协议(协议中声明了取消方法),也调用协议中的取消方法。

好处:

  • 因为其是针对的一个UIImageView,取消前一个操作,省时、省流量
  • 避免SDWebImage的复用。 也就是避免对一张图片进行重复下载。加载图片完成后, 回调时会先检查任务的Operation还在不在, 不在,则不回调显示, 反之回调显示并移除Operation.
  • 当程序中断导致链接失效时,当前的下载还在操作队列中,但是按道理应该是失效状态,我们可以通过先取消当前正在进行的下载来保证操作队列中的链接不存在这种情况。

2. 创建图片管理器

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

如果没有创建过,就使用单例创建;如果创建过,就删除context中的管理器,避免保留循环。

3. 设置占位图

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

这段代码主要用于根据特定条件设置占位图像,并在需要时触发弱缓存的同步逻辑。请注意,这段代码可能随着库的更新和重构而发生变化或移除。

4. 判断url

if (url) {
        // 重置进度
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }

判断传入的url是否为空,如果不为空,则获取图片加载进度并重置为0。

5. 加载图像指示器

// 代码片段中的条件编译指令#if SD_UIKIT || SD_MAC用于在UIKit或AppKit环境下执行相应的逻辑。
// 在这段代码中,首先调用sd_startImageIndicator方法来启动图片加载指示器。然后获取self.sd_imageIndicator的值,该值表示图片指示器对象。
#if SD_UIKIT || SD_MAC
        // check and start image indicator
        // 检查并启动图像指示器
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
        // 定义了一个名为combinedProgressBlock的块变量,它会在图片加载进度更新时被调用。该块变量接收三个参数:receivedSize表示已接收的数据大小,expectedSize表示预期的总数据大小,targetURL表示目标URL。
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        // 在块内部,首先检查imageProgress对象是否存在,并根据已接收和预期大小更新其totalUnitCount和completedUnitCount属性,以便跟踪加载进度。
            if (imageProgress) {
                imageProgress.totalUnitCount = expectedSize;
                imageProgress.completedUnitCount = receivedSize;
            }
#if SD_UIKIT || SD_MAC
			// 通过条件编译指令检查imageIndicator对象是否响应updateIndicatorProgress:方法。如果是,则计算进度值,并将其限制在0到1之间。然后,使用dispatch_async将更新进度的代码块调度到主队列中,以在主线程上执行更新操作。
            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
			// 如果存在progressBlock,则调用该块来传递接收大小、预期大小和目标URL。
            if (progressBlock) {
                progressBlock(receivedSize, expectedSize, targetURL);
            }
        };

这段代码片段在SDWebImage库中用于处理图片加载过程中的进度更新。

6. 创建SDWebImageOperation

		@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; }
            // 在加载完成且没有错误的情况下,检查进度是否未被更新。如果是这样,并且进度的totalUnitCount和completedUnitCount都为0,则将其标记为未知进度(SDWebImageProgressUnitCountUnknown)。
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
            }

调用- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nonnull SDInternalCompletionBlock)completedBlock方法,创建下载任务。

6.1 看看上面创建下载任务得源码

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    // 检查url的合法性
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    // 检查url是否是NSString类,如果是转换为url
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    // 检查url是否是NSURL,如果不是,将url置为空
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

	// 新建SDWebImageCombinedOperation对象,将对象的manager设置为self
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;

	// failedURLs是NSMutableSet<NSURL *>,里面保存了失败过的URL。如果url的地址为空,或者该URL请求失败过且没有设置重试SDWebImageRetryFailed选项,则直接直接调用完成。
    BOOL isFailedUrl = NO;
    if (url) {
        SD_LOCK(_failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(_failedURLsLock);
    }

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
        NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
        return operation;
    }

	// 保存SDWebImageCombinedOperation对象
    SD_LOCK(_runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(_runningOperationsLock);
    
    // 预处理选项和上下文参数,以决定管理器的最终结果
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    
    // 启动条目以从缓存加载图像
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

6.2 上方法经过url验证后会开始缓存查找

调用方法:- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock

- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    // 获取要使用的图像缓存
    id<SDImageCache> imageCache;
    if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
        imageCache = context[SDWebImageContextImageCache];
    } else {
        imageCache = self.imageCache;
    }
    // 获取查询缓存类型
    SDImageCacheType queryCacheType = SDImageCacheTypeAll;
    if (context[SDWebImageContextQueryCacheType]) {
        queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
    }
    
    // 检查是否需要查询缓存
    BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
    if (shouldQueryCache) {
        NSString *key = [self cacheKeyForURL:url context:context];
        @weakify(operation);
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            if (!operation || operation.isCancelled) {
                // 图像合并操作被用户取消
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
                [self safelyRemoveOperationFromRunning:operation];
                return;
            } else if (!cachedImage) {
                BOOL mayInOriginalCache = context[SDWebImageContextImageTransformer] || context[SDWebImageContextImageThumbnailPixelSize];
                // 有机会查询原始缓存,而不是下载,然后应用转换
				// 缩略图解码是在SDImageCache的解码部分完成的,它不需要转换的后期处理
                if (mayInOriginalCache) {
                    [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
                    return;
                }
            }
            
            // 继续下载过程
            [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
        }];
    } else {
        // 继续下载过程
        [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    }
}

精简一下就是:

BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
	// 缓存查找
}else {
	// 进行下载操作
}

未完—

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/552401.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis中动态SQL的使用和注意事项说明

文章目录 0、前言1、if2、where3、trim4、choose-when-otherwise5、foreach应用场景1&#xff1a; 通过数组实现批量删除应用场景2&#xff1a; 通过list集合实现批量添加 6、include抽取公共SQL片段 0、前言 MyBatis框架动态SQL技术是根据特定的条件拼接SQL语句的功能&#x…

【计算机图形学】曲线和曲面

模块5 曲线和曲面 一 实验目的 编写曲线和曲面的算法 二 实验内容 1&#xff1a;绘制Bezier曲线&#xff0c;并采用自行设计输入和交互修改数据点的方式。 实验结果如下图所示&#xff1a; 第一步&#xff1a;输入特征多边形的顶点个数&#xff0c;并按照顺序输入顶点的坐…

《心静的力量》读书笔记

让心静下来&#xff0c;战胜一切忧虑 于我们每个人而言&#xff0c;最重要的就是不要去看远方模糊不清的事&#xff0c;而要做手边真实清楚的事。 明天的重担&#xff0c;加上昨天的重担&#xff0c;会成为今天的最大障碍&#xff0c;要把未来同过去一样紧紧地关在门外……未…

将MetaHuman的身体替换为虚幻商城模型的身体

一、准备好MetaHuman模型和虚幻商城模型 1.准备好MetaHuman模型,参考这篇文章 虚幻商城模型转MetaHuman制作MetaHuman并导入UE,同时复制一个MetaHuman模型 2.下载虚幻商城的原始模型,并导入UE 二、将虚幻商城模型的头去掉 1.打开虚幻商城的模型,找到分段 2.在右边点击…

chatgpt赋能Python-pythonapp开发

PythonApp开发&#xff1a;为什么选择Python实现&#xff1f; Python是当今最流行的编程语言之一&#xff0c;尤其在Web应用开发和数据分析领域更是大有作为。本文将探讨Python在App开发领域中的表现&#xff0c;为什么Python可以成为您理想的选择&#xff1f; 1. 简单易学 …

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

leetcode--优先队列

2163,删除元素后的最小差值 给你一个下标从 0 开始的整数数组 nums &#xff0c;它包含 3 * n 个元素。 你可以从 nums 中删除 恰好 n 个元素&#xff0c;剩下的 2 * n 个元素将会被分成两个 相同大小 的部分。 前面 n 个元素属于第一部分&#xff0c;它们的和记为 sumfirst …

RabbitMQ如何保证顺序性

1. RabbitMQ消息顺序性说明 顺序性&#xff1a; 消息的顺序性是指消费者消费到消息和发送者发布的消息的顺序是一致的 举个例子&#xff0c;不考虑消息重复的情况下&#xff0c;如果生产者发布的消息分别为msg1、msg2、msg3 那么消费者必然也是按照 msg1、msg2、msg3 的顺序来…

【数据结构】--- 博主拍了拍你并向你扔了一“堆”二叉树(堆的概念+结构+代码实现)

文章目录 前言&#x1f31f;一、二叉树的顺序结构及实现&#xff1a;&#x1f31f;二、堆的概念及结构&#xff1a;&#x1f31f;三、堆的代码实现&#xff1a;&#x1f30f;3.1 堆的创建&#xff1a;&#x1f30f;3.2 堆的结构&#xff1a;&#x1f30f;3.3 初始化&#xff1a…

Spring Security 如何实现身份认证和授权?

Spring Security 是一个开源的安全框架&#xff0c;提供了基于权限的访问控制、身份认证、安全性事件发布等功能。在 Spring Boot 应用中使用 Spring Security 可以非常方便地实现用户身份认证和授权。 Spring Security 实现身份认证的主要方式是使用认证过滤器链&#xff0c;…

C语言入门级小游戏——扫雷

文章目录 游戏思路游戏文件的创建游戏菜单棋盘的创建初始化棋盘打印棋盘 布置雷排查雷game.h —— 游戏函数的声明game.c —— 游戏函数的实现test.c —— 游戏的测试 今天我们写一个小游戏——扫雷来增加我们对编程的兴趣 希望这篇文章对友友们有帮助! 游戏思路 游戏文件的创…

Linux:iptables防火墙(SNAT和DNAT)

Linux&#xff1a;iptables防火墙 一、SNAT策略及应用1.1 SNAT原理1.2 SNAT应用 二、DNAT策略及应用2.1 DNAT原理2.2 DNAT应用 一、SNAT策略及应用 1.1 SNAT原理 SNAT 应用环境&#xff1a;局域网主机共享单个公网IP地址接入Internet&#xff08;私有不能在Internet中正常路由…

MySQL数据库笔记——进阶篇

文章目录 存储引擎MySQL体系结构存储引擎简介InnoDB介绍MyISAMMemory 存储引擎的选择小结 索引概述索引结构概述BtreeBTreeHash 存储引擎 MySQL体系结构 连接层&#xff1a; 最上层是一些客户端和链接服务&#xff0c;主要完成一些类似于连接处理、授权认证、及相关的安全方案…

《计算机网络—自顶向下方法》 Wireshark实验(十):NAT 协议分析

NAT&#xff08;Network Address Translation&#xff09;网络地址转换&#xff0c;即在私有地址和全局地址之间转换的协议。私有地址是不能用在 Internet 上(路由器将丢弃寻址这种地址的包)的内部地址。这些地址是不能够在公网上面用的&#xff0c;只能用在局域网的内部。私有…

win安装Nodejs

文章目录 1、安装环境2、安装步骤3、更换npm源为淘宝镜像4、更多node版本下载 1、安装环境 node.js下载官网: nodejs官网 点击选中图标下载即可&#xff1a; 2、安装步骤 1、双击安装包&#xff0c;一直点击next 2、点击change按钮&#xff0c;更换到自己的指定安装位置&…

基于fpga的图像处理之3x3_5x5算子模板中值排序

本文的思路框架&#xff1a; ①本文介绍3x3算子模块和5x5算子模块中&#xff0c;矩阵转化成串行数据后&#xff0c;对其排序&#xff0c;并获取矩阵中值数据&#xff1b; ②本例程中采用的FPGA设计技巧&#xff0c;可用于借鉴&#xff0c;一是采用for循环实现串行数据转化并行数…

vite创建vue2项目

使用vite首先需要注意官方给出的兼容性注意 Vite 需要 Node.js 版本 14.18&#xff0c;16。然而&#xff0c;有些模板需要依赖更高的 Node 版本才能正常运行&#xff0c;当你的包管理器发出警告时&#xff0c;请注意升级你的 Node 版本。 1.初始化vite项目 输入以下命令&#…

Spring MVC 是什么?与 Struts 的区别是什么?

Spring MVC是Spring框架中的一个模块&#xff0c;它提供了一种基于MVC&#xff08;Model-View-Controller&#xff09;架构的Web开发方式。与传统的JSP/Servlet开发方式相比&#xff0c;Spring MVC更加灵活、高效&#xff0c;可以帮助开发人员快速构建高质量的Web应用程序。本文…

vue diff算法与虚拟dom知识整理(10) 梳理patch处理相同节点比较的基本逻辑

这次 我们来讲 diff算法处理到 当新旧节点 是同一个节点时的处理 我们之前也说过 如果不是同一个节点 他就会暴力拆旧 把新的插上去 但当他们是同一个节点 需要精细化比较 最做小化更新 这块我们还没有处理 打开我们的案例 打开 patch.js 对应其实就还是这一块还没有写 我们…

PostgreSQL查询引擎——transform expressions之AEXPR_OP

static Node *transformAExprOp(ParseState *pstate, A_Expr *a){Node *lexpr a->lexpr; Node *rexpr a->rexpr; // 操作符左右表达式Node *result;/* Special-case "foo NULL" and "NULL foo" for compatibility with standards-broke…