[iOS] SDWebImage ソース コードの学習 -- 未完了

SDWebImageの主な機能と関連知識

SDWebImage は、iOS および macOS アプリケーションで画像を非同期ダウンロードおよびキャッシュするための人気のあるサードパーティ ライブラリです。これは、次の主要な機能を備えた、ネットワーク イメージの読み込みとキャッシュを処理するためのシンプルかつ強力な方法を提供します。

  1. 非同期ダウンロード: SDWebImage はマルチスレッド メカニズムを使用しており、アプリケーションのユーザー インターフェイスのブロックを避けるために、画像をバックグラウンドで非同期にダウンロードできます。
  2. 画像キャッシュ: ダウンロードした画像をメモリとディスクに自動的に保存できるメモリ キャッシュとディスク キャッシュのメカニズムがあります。こうすることで、その後の読み込み時に、画像を再度ダウンロードすることなく、キャッシュから画像をすばやくフェッチできます。
  3. プレースホルダー画像とプログレッシブ読み込み: SDWebImage は、画像のダウンロード中のプレースホルダー画像の表示とプログレッシブ画像の読み込みをサポートしており、ユーザーは画像の読み込みの進行状況を段階的に確認できます。
  4. キャッシュのクリーニング: SDWebImage にはキャッシュをクリーニングするオプションも用意されており、期限切れになったキャッシュや不要になったキャッシュを必要に応じて手動でクリーニングできます。

ツールクラスとその機能

  • NSData+ImageContentType は、画像データを通じて現在の画像の形式を決定します。
  • SDImageCache キャッシュは、ディスクおよびメモリの 2 次キャッシュ (NSCache) を定義し、キャッシュ シングルトンの管理を担当します。
  • SDWebImageCompat さまざまなプラットフォーム/バージョン/画面などとの互換性を確保するためのマクロ定義とインライン画像スケーリング。
  • SDWebImageDecoder 画像解凍、内部インターフェイスは 1 つだけです
  • SDWebImageDownloader は、非同期イメージのダウンロードを管理し、ダウンロード キューを管理し、操作を管理し、ネットワーク リクエストの処理結果と例外シングルトンを管理し、ネットワーク リクエスト コールバックのブロックを保存します
    。私が理解しているデータ構造は、おそらく
    // Structure {"url":[{"progress)です。 ":"progressBlock ”},{"complete":"completeBlock"}]}

  • SDWebImageDownloaderOperation は、画像の非同期ダウンロード用に NSOperation を実装します。ネットワーク リクエストは、カスタム Operation タスク オブジェクトをダウンロードするために NSURLSession エージェントに与えられます。開始キャンセルおよびその他のメソッドを手動で実装する必要があります。
  • SDWebImageManager コア管理クラスは、主にキャッシュ管理とダウンロード管理をカプセル化しており、メイン インターフェイス downloadImageWithURL は単一インタレストです。
  • SDWebImageOperation 操作プロトコルは、キャンセル操作インターフェイス上の downloaderOperation のプロキシのみを定義します。
  • SDWebImagePrefetcher は、低い優先度で画像を事前ダウンロードし、単に SDWebImageViewManager をカプセル化するために使用されることはほとんどありません。
  • MKAnnotationView+WebCache – MKAnnotationView の画像を非同期的にロードします
  • UIButton+WebCache は UIButton の画像を非同期で読み込みます
  • UIImage+GIF 画像データを指定形式の画像に変換します
  • UIImage+MultiFormat 画像データを指定形式の画像に変換します
  • UIImageView+HighlightedWebCache は UIImageView の画像を非同期的に読み込みます
  • UIImageView+WebCache は UIImageView の画像を非同期で読み込みます
  • UIView+WebCacheOperation は、画像の非同期ダウンロードのために現在の MKAnnotationView / UIButton / UIImageView 操作を保存します。

ダウンロードプロセス

基本的な利用の流れ

ここに画像の説明を挿入します

実装プロセス

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

最初はメソッド名です。

合計 5 つのパラメータがあります。URL はダウンロードする必要があるオンライン画像リンクです。プレースホルダ画像は UIImage タイプです。SDWebImageOptions に関しては、そのソース コードを表示し、関連情報を照会します。これは公開され、可能な A 選択です。ユーザーが使用するメソッド。

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. コンテキスト

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。このクラスは主にダウンロード操作を担当します。関連するオブジェクトを使用して、各 UIKit オブジェクトのメモリ内に OperationDictionary 辞書を維持します。対応するダウンロード操作をさまざまなキー値に追加したり、ダウンロード操作が完了していない場合はキーに基づいて操作をキャンセルしたりできます。

通常、operationDictionary のキーはクラス名です。このように、同じ UIImageView が同時に 2 回呼び出された場合、最初のダウンロード操作が最初にキャンセルされ、次に、operationDictionary 内の操作が 2 番目のダウンロード操作にマップされます。

次に、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;
    }
}

キー値によって関連付けられたオブジェクトを返します。操作が空の場合は、オブジェクトを作成し、それをキー値にバインドします。

2 つの同一のキー値を入力した場合、取得される関連付けられたオブジェクトは同じになります。受信キーが検索され、返された辞書にすでに存在するかどうかが確認されます。存在する場合は、すべての操作がキャンセルされます。conformsToProtocol メソッドがこのプロトコルに準拠している場合 (キャンセル メソッドがプロトコルで宣言されている場合)、キャンセル メソッドも呼び出されます。プロトコルで。

利点:

  • これは UIImageView を対象としているため、前の操作をキャンセルすると時間とトラフィックが節約されます。
  • SDWebImage の再利用は避けてください。これは、イメージを繰り返しダウンロードすることを避けるためです。イメージの読み込みが完了した後、コールバックはまずタスクの操作がまだ存在するかどうかを確認します。存在しない場合、コールバックは表示されません。そうでない場合は、コールバックが表示され、操作が削除されます。
  • プログラムの中断によりリンクに障害が発生した場合、現在のダウンロードはまだ操作キュー内にありますが、無効な状態になっている必要があります。最初に現在のダウンロードをキャンセルすることで、操作キュー内のリンクにこの状況が存在しないことを確認できます。進行中のダウンロード。

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

マネージャーが作成されていない場合は、シングルトンを使用して作成します。作成されている場合は、コンテキスト内のマネージャーを削除して、保持サイクルを回避します。

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);
        // 如果需要进行缓存查询,首先生成查询缓存的键值key,然后使用imageCache对象调用queryImageForKey:options:context:cacheType:completion:方法来进行缓存查询。
        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 {
    
    
	// 进行下载操作
}

6.3 クエリ機能:

- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock {
    
    
	// 检查参数
    if (!key) {
    
    
        return nil;
    }
    NSArray<id<SDImageCache>> *caches = self.caches;
    // 获取缓存数量
    NSUInteger count = caches.count;
    if (count == 0) {
    
    
        return nil;
    } else if (count == 1) {
    
    
        return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
    }
    switch (self.queryOperationPolicy) {
    
    
        case SDImageCachesManagerOperationPolicyHighestOnly: {
    
    
            id<SDImageCache> cache = caches.lastObject;
            return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
        }
            break;
        case SDImageCachesManagerOperationPolicyLowestOnly: {
    
    
            id<SDImageCache> cache = caches.firstObject;
            return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
        }
            break;
        case SDImageCachesManagerOperationPolicyConcurrent: {
    
    
            SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
            [operation beginWithTotalCount:caches.count];
            [self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
            return operation;
        }
            break;
        case SDImageCachesManagerOperationPolicySerial: {
    
    
            SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
            [operation beginWithTotalCount:caches.count];
            [self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
            return operation;
        }
            break;
        default:
            return nil;
            break;
    }
}

このメソッドは、キャッシュの数に応じて異なる処理を実行します。

  1. キャッシュ量が 0 の場合は、直接空が返されます。
  2. キャッシュの数が 1 の場合、最初のキャッシュ オブジェクトのメソッドが直接呼び出されqueryImageForKey:options:context:cacheType:completion:、パラメータが透過的にそれに渡されます。
  3. キャッシュの数が 1 より大きい場合、設定されたクエリ操作ポリシー queryOperationPolicy に従って異なる処理が実行されます。
    • ポリシーが SDImageCachesManagerOperationPolicyHighestOnly の場合、最後のキャッシュ オブジェクトがクエリ用に選択されます。
    • ポリシーが SDImageCachesManagerOperationPolicyLowestOnly の場合、最初のキャッシュ オブジェクトがクエリ用に選択されます。
    • ポリシーが SDImageCachesManagerOperationPolicyConcurrent の場合、クエリ操作として SDImageCachesManagerOperation オブジェクトを作成し、同時クエリのためにキャッシュ オブジェクトを 1 つずつ走査します。
    • ポリシーが SDImageCachesManagerOperationPolicySerial の場合、クエリ操作として SDImageCachesManagerOperation オブジェクトを作成し、シリアル クエリのためにキャッシュ オブジェクトを 1 つずつ走査します。

6.4 上記の関数はさらに SDImageCache クラスの同名の関数を呼び出します

ソースコード:

- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
    
    
    SDImageCacheOptions cacheOptions = 0;
    if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
    if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
    if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
    if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
    if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
    if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;
    
    return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}

このコードは、渡されたオプションに基づいて、対応するキャッシュ オプションcacheOptions を設定し、次にqueryCacheOperationForKey:options:context:cacheType:done: メソッドを呼び出してキャッシュ クエリ操作を実行し、クエリ操作のオブジェクトを返します。

キャッシュ オプションcacheOptions には次のものが含まれます。

  • SDWebImageQueryMemoryData: メモリ キャッシュ データをクエリします。
  • SDWebImageQueryMemoryDataSync: メモリ キャッシュ データを同期的にクエリします。
  • SDWebImageQueryDiskDataSync: ディスク キャッシュ データを同期的にクエリします。
  • SDWebImageScaleDownLargeImages: 大きな画像のサイズを縮小します。
  • SDWebImageAvoidDecodeImage: 画像のデコードを回避します。
  • SDWebImageDecodeFirstFrameOnly: アニメーション画像の最初のフレームのみをデコードします。
  • SDWebImagePreloadAllFrames: アニメーション画像のすべてのフレームをプリロードします。
  • SDWebImageMatchAnimatedImageClass: アニメーション画像と一致するクラス。

6.5 実際にキャッシュクエリ操作を実行する関数を呼び出す

ソースコード:

- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    
    
    if (!key) {
    
    
        if (doneBlock) {
    
    
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // 无效的缓存类型
    if (queryCacheType == SDImageCacheTypeNone) {
    
    
        if (doneBlock) {
    
    
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    
    // 首先检查内存缓存…
    UIImage *image;
    if (queryCacheType != SDImageCacheTypeDisk) {
    
    
        image = [self imageFromMemoryCacheForKey:key];
    }
    
    if (image) {
    
    
        if (options & SDImageCacheDecodeFirstFrameOnly) {
    
    
            // 确保静态图像
            Class animatedImageClass = image.class;
            if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
    
    
#if SD_MAC
                image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
            }
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
    
    
            // 检查图像类匹配
            Class animatedImageClass = image.class;
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
    
    
                image = nil;
            }
        }
    }

    BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
    
    
        if (doneBlock) {
    
    
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    // 第二,检查磁盘缓存…
    SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];
    operation.key = key;
    // 检查是否需要同步查询磁盘
    // 1 内存缓存命中& memoryDataSync
	// 2 内存缓存丢失& diskDataSync
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    NSData* (^queryDiskDataBlock)(void) = ^NSData* {
    
    
        @synchronized (operation) {
    
    
            if (operation.isCancelled) {
    
    
                return nil;
            }
        }
        
        return [self diskImageDataBySearchingAllPathsForKey:key];
    };
    
    UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) {
    
    
        @synchronized (operation) {
    
    
            if (operation.isCancelled) {
    
    
                return nil;
            }
        }
        
        UIImage *diskImage;
        if (image) {
    
    
            // 图像来自内存缓存,但需要图像数据
            diskImage = image;
        } else if (diskData) {
    
    
            BOOL shouldCacheToMomery = YES;
            if (context[SDWebImageContextStoreCacheType]) {
    
    
                SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
                shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
            }
            if (context[SDWebImageContextImageThumbnailPixelSize]) {
    
    
                // 查询生成缩略图的全大小缓存键,不应该写回全大小内存缓存
                shouldCacheToMomery = NO;
            }
            // 只有在内存缓存丢失时才解码图像数据
            diskImage = [self diskImageForKey:key data:diskData options:options context:context];
            if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
    
    
                NSUInteger cost = diskImage.sd_memoryCost;
                [self.memoryCache setObject:diskImage forKey:key cost:cost];
            }
        }
        return diskImage;
    };
    
    // 在ioQueue中查询以保证io安全
    if (shouldQueryDiskSync) {
    
    
        __block NSData* diskData;
        __block UIImage* diskImage;
        dispatch_sync(self.ioQueue, ^{
    
    
            diskData = queryDiskDataBlock();
            diskImage = queryDiskImageBlock(diskData);
        });
        if (doneBlock) {
    
    
            doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
        }
    } else {
    
    
        dispatch_async(self.ioQueue, ^{
    
    
            NSData* diskData = queryDiskDataBlock();
            UIImage* diskImage = queryDiskImageBlock(diskData);
            @synchronized (operation) {
    
    
                if (operation.isCancelled) {
    
    
                    return;
                }
            }
            if (doneBlock) {
    
    
                dispatch_async(dispatch_get_main_queue(), ^{
    
    
                    //从IO队列调度到主队列需要时间,用户可以在调度期间调用cancel
					//这个检查是为了避免双重回调(一个是来自' SDImageCacheToken '同步)
                    @synchronized (operation) {
    
    
                        if (operation.isCancelled) {
    
    
                            return;
                        }
                    }
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        });
    }
    
    return operation;
}
  1. メモリ キャッシュ ルックアップ、その使用imageFromMemoryCacheForKey:方法
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    
    
    return [self.memoryCache objectForKey:key];
}

ここでは、キーを介してキャッシュを直接検索します。ここでキャッシュが見つかり、ディスクをクエリする強制的な指示がない場合は、直接コールバックされます。

    BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
    
    
        if (doneBlock) {
    
    
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
  1. ディスクキャッシュルックアップ、その使用setObject:forKey:cost:方法

前のステップで見つからなかった場合は、ディスク検索段階に入り、新しい NSOperation が作成され、queryDiskBlock コード ブロックが実行されます。

  • キーでディスクを検索し、diskData を取得します
  • 前のステップでイメージがメモリ内で見つかった場合は、直接割り当てられます。そうでない場合は、diskData がデコードされ、ディスクからのイメージがメモリに書き込まれます。
[self.memoryCache setObject:diskImage forKey:key cost:cost];

6.6 ダウンロードメソッドの呼び出し

ダウンロード メソッドは、すべての検索が見つからなかった場合に呼び出されます。

まず、queryImageForKey:options:context: cacheType:completion:コールバック ブロックでダウンロードが必要かどうかを判断します。

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

ダウンロードが必要な場合は、SDWebImageDownloader の callDownloadProcessForOperation を呼び出します。

- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                    url:(nonnull NSURL *)url
                                options:(SDWebImageOptions)options
                                context:(SDWebImageContext *)context
                            cachedImage:(nullable UIImage *)cachedImage
                             cachedData:(nullable NSData *)cachedData
                              cacheType:(SDImageCacheType)cacheType
                               progress:(nullable SDImageLoaderProgressBlock)progressBlock
                              completed:(nullable SDInternalCompletionBlock)completedBlock {
    
    
    // 标记缓存操作结束
    @synchronized (operation) {
    
    
        operation.cacheOperation = nil;
    }
    
    // 抓取要使用的图像加载器
    id<SDImageLoader> imageLoader;
    if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
    
    
        imageLoader = context[SDWebImageContextImageLoader];
    } else {
    
    
        imageLoader = self.imageLoader;
    }
    
    // 检查我们是否需要从网络下载图像
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
    
    
        shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
    } else {
    
    
        shouldDownload &= [imageLoader canRequestImageForURL:url];
    }
    if (shouldDownload) {
    
    
        if (cachedImage && options & SDWebImageRefreshCached) {
    
    
			// 如果在缓存中找到图像,但是提供了SDWebImageRefreshCached,则通知缓存的图像
			// 并尝试重新下载,以便有机会NSURLCache从服务器刷新它。
            [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            // 将缓存的图像传递给图像加载器。图像加载器应该检查远程图像是否等于缓存的图像。
            SDWebImageMutableContext *mutableContext;
            if (context) {
    
    
                mutableContext = [context mutableCopy];
            } else {
    
    
                mutableContext = [NSMutableDictionary dictionary];
            }
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        
        @weakify(operation);
        operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
    
    
            @strongify(operation);
            if (!operation || operation.isCancelled) {
    
    
                // 图像合并操作被用户取消
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{
    
    NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
            } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
    
    
                // 图像刷新击中NSURLCache缓存,不调用完成块
            } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
    
    
                // 下载操作在发送请求前被用户取消,不阻止失败的URL
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
            } else if (error) {
    
    
                [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
                BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
                
                if (shouldBlockFailedURL) {
    
    
                    SD_LOCK(self->_failedURLsLock);
                    [self.failedURLs addObject:url];
                    SD_UNLOCK(self->_failedURLsLock);
                }
            } else {
    
    
                if ((options & SDWebImageRetryFailed)) {
    
    
                    SD_LOCK(self->_failedURLsLock);
                    [self.failedURLs removeObject:url];
                    SD_UNLOCK(self->_failedURLsLock);
                }
                // 继续存储缓存进程
                [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
            }
            
            if (finished) {
    
    
                [self safelyRemoveOperationFromRunning:operation];
            }
        }];
    } else if (cachedImage) {
    
    
        [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    } else {
    
    
        // 图像不在缓存中,委托不允许下载
        [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    }
}

このコードは、渡されたパラメーターに基づいて画像をダウンロードする必要があるかどうかを判断し、状況に応じて画像をダウンロードするために画像ローダーを呼び出します。

画像をダウンロードする必要がある場合は、まずキャッシュされた画像があり、オプションに SDWebImageRefreshCached が含まれているかどうかを確認します。条件が満たされる場合、まずキャッシュされたイメージについて完了ブロックに通知し、次にキャッシュされたイメージをイメージ ローダーに渡し、キーと値のペア SDWebImageContextLoaderCachedImage をコンテキスト コンテキストに追加して、キャッシュされたイメージを比較のためにイメージ ローダーに渡します。次に、イメージ ローダーrequestImageWithURL:options:context:progress:completed:メソッドを呼び出し、URL アドレス url、オプション、コンテキスト、進行状況ブロック progressBlock および完了ブロック completedBlock を渡して、イメージのダウンロード操作を実行します。

requestImageWithURL:options:context:progress:completed:方法について:

- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
    
    
    UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
    
    SDWebImageDownloaderOptions downloaderOptions = 0;
    if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
    if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
    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 (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
    if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;
    
    if (cachedImage && options & SDWebImageRefreshCached) {
    
    
        // force progressive off if image already cached but forced refreshing
        downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
        // ignore image read from NSURLCache if image if cached but force refreshing
        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
    }
    
    return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}

このコードは、コンテキスト コンテキストに基づいてキャッシュされたイメージ CachedImage を取得し、受信オプションのオプションやその他の条件に基づいてイメージ ダウンローダー オプション downloaderOptions を設定します。

downloaderOptions オプションには次のものが含まれます。

  • SDWebImageLowPriority: 低優先度のダウンロード。
  • SDWebImageProgressiveLoad: プログレッシブ読み込み。
  • SDWebImageRefreshCached: キャッシュを強制的に更新します。
  • SDWebImageContinueInBackground: バックグラウンドでダウンロードを続行します。
  • SDWebImageHandleCookies: Cookie を処理します。
  • SDWebImageAllowInvalidSSLCertificates: 無効な SSL 証明書を許可します。
  • SDWebImageHighPriority: 高優先度のダウンロード。
  • SDWebImageScaleDownLargeImages: ダウンロードされた大きな画像を拡大縮小します。
  • SDWebImageAvoidDecodeImage: 画像のデコードを回避します。
  • SDWebImageDecodeFirstFrameOnly: 最初のフレームのみをデコードします。
  • SDWebImagePreloadAllFrames: すべてのフレームをプリロードします。
  • SDWebImageMatchAnimatedImageClass: アニメーション画像と一致するクラス。

キャッシュされたイメージcachedImageが存在し、オプションにSDWebImageRefreshCachedが含まれている場合は、オプションdownloaderOptionsでプログレッシブ・ロード・フラグとNSURLCacheからイメージを読み取るためのフラグをオフにします。

最後に、イメージ ダウンローダーdownloadImageWithURL:options:context:progress:completed:メソッドを呼び出して、イメージのダウンロード操作を実行します。

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    
    
    // URL将被用作回调字典的键,因此它不能为nil。如果为nil,则立即调用没有图像或数据的已完成块。
    if (url == nil) {
    
    
        if (completedBlock) {
    
    
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{
    
    NSLocalizedDescriptionKey : @"Image url is nil"}];
            completedBlock(nil, nil, error, YES);
        }
        return nil;
    }
    
    SD_LOCK(_operationsLock);
    id downloadOperationCancelToken;
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    // 有一种情况,操作可能被标记为完成或取消,但没有从' self. urlooperations '中删除。
    if (!operation || operation.isFinished || operation.isCancelled) {
    
    
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        if (!operation) {
    
    
            SD_UNLOCK(_operationsLock);
            if (completedBlock) {
    
    
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{
    
    NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
                completedBlock(nil, nil, error, YES);
            }
            return nil;
        }
        @weakify(self);
        operation.completionBlock = ^{
    
    
            @strongify(self);
            if (!self) {
    
    
                return;
            }
            SD_LOCK(self->_operationsLock);
            [self.URLOperations removeObjectForKey:url];
            SD_UNLOCK(self->_operationsLock);
        };
        self.URLOperations[url] = operation;
        // 在提交到操作队列之前添加处理程序,避免操作在设置处理程序之前完成的竞争情况。
        downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        // 根据Apple的文档完成所有配置后,才将操作添加到操作队列。
		//  ' addOperation: '不会同步执行' operation.completionBlock ',因此不会导致死锁。
        [self.downloadQueue addOperation:operation];
    } else {
    
    
		// 当我们重用下载操作来附加更多回调时,可能会有线程安全问题,因为回调的getter可能在另一个队列中(解码队列或委托队列)
		// 所以我们在这里锁定操作,在' SDWebImageDownloaderOperation '中,我们使用' @synchonzied (self) ',以确保这两个类之间的线程安全。
        @synchronized (operation) {
    
    
            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        }
        if (!operation.isExecuting) {
    
    
            if (options & SDWebImageDownloaderHighPriority) {
    
    
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
    
    
                operation.queuePriority = NSOperationQueuePriorityLow;
            } else {
    
    
                operation.queuePriority = NSOperationQueuePriorityNormal;
            }
        }
    }
    SD_UNLOCK(_operationsLock);
    
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    
    return token;
}

このコードは、渡されたパラメーターに基づいてイメージのダウンロード操作を作成または取得し、対応する設定と処理を実行します。

  1. URL アドレス url が nil かどうかを確認します。nil の場合、完了ブロック completedBlock がすぐに呼び出され、エラー オブジェクトが返され、ピクチャやデータは存在しません。
  2. _operationsLock をロックし、URL アドレス url に従って self.URLOperations 辞書からダウンロード操作 Operation を取得します。ダウンロード操作が存在しない場合、または完了またはキャンセルされた場合は、新しいダウンロード操作オペレーションが作成され、self.URLOperations ディクショナリに追加されます。同時に、self.URLOperations 内の対応するエントリの削除など、操作の完了後にコールバックを設定します。progressBlock と completedBlock をオペレーションに追加し、キャンセル トークン downloadOperationCancelToken を返します。最後に、ダウンロード キュー downloadQueue に操作を追加します。
  3. ダウンロード操作がすでに存在する場合は、操作をロックし、進行状況ブロック progressBlock と完了ブロック completedBlock を操作に追加します。操作が実行されない場合、操作のキューの優先順位はオプションに従って設定されます。次に、_operationsLock のロックを解除します。
  4. 最後にSDWebImageDownloadTokenオブジェクトトークンを作成し、そのトークンにオペレーションoperation、URLアドレスurl、リクエストオブジェクトoperation.request、キャンセルトークンdownloadOperationCancelTokenを設定します。最後にトークンを返します。

このコードはダウンロード操作を作成し、createDownloaderOperationWithUrl:options:context:メソッドを呼び出します。最後に、このメソッドを見てみましょう。

- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options
                                                                                  context:(nullable SDWebImageContext *)context {
    
    
    NSTimeInterval timeoutInterval = self.config.downloadTimeout;
    if (timeoutInterval == 0.0) {
    
    
        timeoutInterval = 15.0;
    }
    
    // 为了防止潜在的重复缓存(NSURLCache + SDImageCache),如果被告知其他情况,我们禁用图像请求的缓存
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
    mutableRequest.HTTPShouldUsePipelining = YES;
    SD_LOCK(_HTTPHeadersLock);
    mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
    SD_UNLOCK(_HTTPHeadersLock);
    
    // 上下文选项
    SDWebImageMutableContext *mutableContext;
    if (context) {
    
    
        mutableContext = [context mutableCopy];
    } else {
    
    
        mutableContext = [NSMutableDictionary dictionary];
    }
    
    // 请求修改器
    id<SDWebImageDownloaderRequestModifier> requestModifier;
    if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
    
    
        requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
    } else {
    
    
        requestModifier = self.requestModifier;
    }
    
    NSURLRequest *request;
    if (requestModifier) {
    
    
        NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
        // 如果修改后的请求为nil,则提前返回
        if (!modifiedRequest) {
    
    
            return nil;
        } else {
    
    
            request = [modifiedRequest copy];
        }
    } else {
    
    
        request = [mutableRequest copy];
    }
    // 反应修饰符
    id<SDWebImageDownloaderResponseModifier> responseModifier;
    if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
    
    
        responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
    } else {
    
    
        responseModifier = self.responseModifier;
    }
    if (responseModifier) {
    
    
        mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
    }
    // 解密
    id<SDWebImageDownloaderDecryptor> decryptor;
    if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {
    
    
        decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];
    } else {
    
    
        decryptor = self.decryptor;
    }
    if (decryptor) {
    
    
        mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;
    }
    
    context = [mutableContext copy];
    
    // 操作类
    Class operationClass = self.config.operationClass;
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
    
    
        // 自定义操作类
    } else {
    
    
        operationClass = [SDWebImageDownloaderOperation class];
    }
    NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
    
    if ([operation respondsToSelector:@selector(setCredential:)]) {
    
    
        if (self.config.urlCredential) {
    
    
            operation.credential = self.config.urlCredential;
        } else if (self.config.username && self.config.password) {
    
    
            operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
        }
    }
        
    if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
    
    
        operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
    }
    
    if ([operation respondsToSelector:@selector(setAcceptableStatusCodes:)]) {
    
    
        operation.acceptableStatusCodes = self.config.acceptableStatusCodes;
    }
    
    if ([operation respondsToSelector:@selector(setAcceptableContentTypes:)]) {
    
    
        operation.acceptableContentTypes = self.config.acceptableContentTypes;
    }
    
    if (options & SDWebImageDownloaderHighPriority) {
    
    
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
    
    
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    
    if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
    
    
        // 系统地模拟后进先出的执行顺序,每个之前的加法操作都可以依赖于新操作
		// 这可以保证新操作首先被执行,即使当一些操作完成时,你同时追加新的操作
		// 仅使最后添加的操作依赖于新操作不能解决这个问题。参见测试用例#test15DownloaderLIFOExecutionOrder
        for (NSOperation *pendingOperation in self.downloadQueue.operations) {
    
    
            [pendingOperation addDependency:operation];
        }
    }
    
    return operation;
}

全体として、このコードは、渡された URL、オプション、およびコンテキストに基づいて画像をダウンロードするためのアクション オブジェクトを作成します。リクエストのタイムアウト、キャッシュ ポリシー、その他のプロパティを設定するとともに、構成内の情報に基づいて操作の資格情報、キューの優先順位なども設定します。リクエストとレスポンスの修飾子と復号化子は、コンテキスト内のオプションを介して適用することもできます。最後に、作成された操作オブジェクトは後で使用するために返されます。

このコードは、SDWebImage ライブラリの画像ダウンロードのコア ロジックの 1 つであり、ダウンロード操作の作成と構成を処理するために使用されます。

次に、NSURLSession を使用してダウンロードするため、SDWebImageDownloader には NSURLSession プロキシ メソッドが存在します。

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

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
    
    
        [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
    } else {
    
    
        if (completionHandler) {
    
    
            completionHandler(NSURLSessionResponseAllow);
        }
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
    
    
        [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    
    

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
    
    
        [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
    } else {
    
    
        if (completionHandler) {
    
    
            completionHandler(proposedResponse);
        }
    }
}

#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
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
    if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
    
    
        [dataOperation URLSession:session task:task didCompleteWithError:error];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    
    
    
    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
    if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
    
    
        [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
    } else {
    
    
        if (completionHandler) {
    
    
            completionHandler(request);
        }
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
    if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
    
    
        [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
    } else {
    
    
        if (completionHandler) {
    
    
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        }
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
    
    
    
    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
    if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
    
    
        [dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
    }
}

これらのメソッドはすべて、(NSOperation *)operationWithTask:(NSURLSessionTask *)task を呼び出します。
タスクを通じて対応する SDWebImageDownloaderOperation インスタンスを取得し、特定の操作をこのクラスに配置します。このクラスでは、デコードする必要があるピクチャに対してデコード操作を実行し、ピクチャをスケーリングする必要があるかどうかを判断します。

6.6 画像のキャッシュ

関数の完了ブロックにコールバックしますrequestImageWithURL:options:context:progress:completed:。ダウンロードが成功した場合は、callStoreCacheProcessForOperation:url:options:context:downloadedImage:downloadedData:cacheType:finished:completed関数を呼び出して進行状況をキャッシュします。

- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                      url:(nonnull NSURL *)url
                                  options:(SDWebImageOptions)options
                                  context:(SDWebImageContext *)context
                          downloadedImage:(nullable UIImage *)downloadedImage
                           downloadedData:(nullable NSData *)downloadedData
                                cacheType:(SDImageCacheType)cacheType
                                 finished:(BOOL)finished
                                completed:(nullable SDInternalCompletionBlock)completedBlock {
    
    
    // 抓取要使用的图像缓存,首先选择独立的原始缓存
    id<SDImageCache> imageCache;
    if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
    
    
        imageCache = context[SDWebImageContextOriginalImageCache];
    } else {
    
    
        // 如果没有可用的独立缓存,则使用默认缓存
        if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
    
    
            imageCache = context[SDWebImageContextImageCache];
        } else {
    
    
            imageCache = self.imageCache;
        }
    }
    BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
    // 目标映像存储缓存类型
    SDImageCacheType storeCacheType = SDImageCacheTypeAll;
    if (context[SDWebImageContextStoreCacheType]) {
    
    
        storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
    }
    // 原始存储映像缓存类型
    SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
    if (context[SDWebImageContextOriginalStoreCacheType]) {
    
    
        originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
    }
    id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
    if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
    
    
        transformer = nil;
    }
    id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
    
    // 变压器检查
    BOOL shouldTransformImage = downloadedImage && transformer;
    shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
    shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
    // 缩略图检查
    BOOL shouldThumbnailImage = context[SDWebImageContextImageThumbnailPixelSize] != nil || downloadedImage.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize] != nil;
    
    BOOL shouldCacheOriginal = downloadedImage && finished && cacheType == SDImageCacheTypeNone;
    
    // 如果可用,将原始图像存储到缓存中
    if (shouldCacheOriginal) {
    
    
        // 获取原始缓存密钥生成,不需要转换/缩略图
        NSString *key = [self originalCacheKeyForURL:url context:context];
        // 通常使用存储缓存类型,但如果目标图像被转换,则使用原始存储缓存类型代替
        SDImageCacheType targetStoreCacheType = (shouldTransformImage || shouldThumbnailImage) ? originalStoreCacheType : storeCacheType;
        UIImage *fullSizeImage = downloadedImage;
        if (shouldThumbnailImage) {
    
    
            // 缩略图解码不保留原始图像
			// 这里我们只存储原始数据到磁盘的原始缓存键
			// 在' storeTransformCacheProcess '中将缩略图存储到内存中以获取缩略图缓存键
            fullSizeImage = nil;
        }
        if (fullSizeImage && cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
    
    
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    
    
                @autoreleasepool {
    
    
                    NSData *cacheData = [cacheSerializer cacheDataWithImage:fullSizeImage originalData:downloadedData imageURL:url];
                    [self storeImage:fullSizeImage imageData:cacheData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
    
    
                        // 继续转换过程
                        [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
                    }];
                }
            });
        } else {
    
    
            [self storeImage:fullSizeImage imageData:downloadedData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
    
    
                // 继续转换过程
                [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
            }];
        }
    } else {
    
    
        // 继续转换过程
        [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
    }
}

この関数は画像キャッシュに関するいくつかの設定を実行し、主にキャッシュ機能を担当しますstoreImage:imageData:forKey:imageCache:cacheType:waitStoreCache:completion:この関数を見てみましょう。

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)data
            forKey:(nullable NSString *)key
        imageCache:(nonnull id<SDImageCache>)imageCache
         cacheType:(SDImageCacheType)cacheType
    waitStoreCache:(BOOL)waitStoreCache
        completion:(nullable SDWebImageNoParamsBlock)completion {
    
    
    // 检查我们是否应该等待存储缓存完成。如果没有,立即回调
    [imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
    
    
        if (waitStoreCache) {
    
    
            if (completion) {
    
    
                completion();
            }
        }
    }];
    if (!waitStoreCache) {
    
    
        if (completion) {
    
    
            completion();
        }
    }
}

この関数は、ストレージ キャッシュが完了するまで待つ必要があるかどうかを確認し、storeImage:imageData:orKey:cacheType:completion:関数を呼び出すだけであることがわかりました。続きを読みましょう:

- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    
    
    switch (cacheType) {
    
    
        case SDImageCacheTypeNone: {
    
    
            [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock];
        }
            break;
        case SDImageCacheTypeMemory: {
    
    
            [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock];
        }
            break;
        case SDImageCacheTypeDisk: {
    
    
            [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock];
        }
            break;
        case SDImageCacheTypeAll: {
    
    
            [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock];
        }
            break;
        default: {
    
    
            if (completionBlock) {
    
    
                completionBlock();
            }
        }
            break;
    }
}

ご覧のとおり、この関数はキャッシュ メソッドのいくつかのオプションを設定し、実際のキャッシュ関数を呼び出しますstoreImage:imageData:forKey:toMemory:toDisk:completion:

最後にこの関数を見てみましょう。

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
          toMemory:(BOOL)toMemory
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    
    
    if ((!image && !imageData) || !key) {
    
    
        if (completionBlock) {
    
    
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    if (image && toMemory && self.config.shouldCacheImagesInMemory) {
    
    
        NSUInteger cost = image.sd_memoryCost;
        [self.memoryCache setObject:image forKey:key cost:cost];
    }
    
    if (!toDisk) {
    
    
        if (completionBlock) {
    
    
            completionBlock();
        }
        return;
    }
    dispatch_async(self.ioQueue, ^{
    
    
        @autoreleasepool {
    
    
            NSData *data = imageData;
            if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
    
    
                // If image is custom animated image class, prefer its original animated data
                data = [((id<SDAnimatedImage>)image) animatedImageData];
            }
            if (!data && image) {
    
    
                // Check image's associated image format, may return .undefined
                SDImageFormat format = image.sd_imageFormat;
                if (format == SDImageFormatUndefined) {
    
    
                    // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
                    if (image.sd_isAnimated) {
    
    
                        format = SDImageFormatGIF;
                    } else {
    
    
                        // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
                        format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG;
                    }
                }
                data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
            }
            [self _storeImageDataToDisk:data forKey:key];
            [self _archivedDataWithImage:image forKey:key];
        }
        
        if (completionBlock) {
    
    
            dispatch_async(dispatch_get_main_queue(), ^{
    
    
                completionBlock();
            });
        }
    });
}

self.memCache がメモリに直接保存されていることがわかります。

if (image && toMemory && self.config.shouldCacheImagesInMemory) {
    
    
        NSUInteger cost = image.sd_memoryCost;
        [self.memoryCache setObject:image forKey:key cost:cost];
    }

ディスクに保存する必要がある場合は、非同期キュー self.ioQueue のメソッドを呼び出してディスクに書き込みます。
すべてのキャッシュには時間制限とサイズ制限があります。SDImageCache には一連のクリーンアップ戦略があります。デフォルトの有効キャッシュ期間画像は7日間です:

  1. メモリのクリーニング
[self.memCache removeAllObjects];
  1. ディスクのクリーンアップ

SDWebImageはアプリがバックグラウンドに移行する通知を監視しており、バックグラウンドに移行する際、現在のキャッシュ量が設定した最大キャッシュ量以上の場合、キャッシュ量が条件を満たすまで一般コンテンツを時系列に再帰的に削除します。

7 画像をダウンロードする

画像のダウンロード操作:

[self sd_setImageLoadOperation:operation forKey:validOperationKey];

その実装を見てみましょう。

- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
    
    
    if (key) {
    
    
        [self sd_cancelImageLoadOperationWithKey:key];
        if (operation) {
    
    
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            @synchronized (self) {
    
    
                [operationDictionary setObject:operation forKey:key];
            }
        }
    }
}

すべての操作は、operationDictionary 辞書によって維持されます。新しい操作を実行する前に、すべての操作をキャンセルしてください。

要約する

この方法には多くの方法が含まれますが、そのプロセスについて簡単に説明します。

  1. SDWebImageContext をコピーして可変に変換し、検証 ID として validOperationKey 値を取得します。デフォルト値は現在のビューのクラス名です。
  2. sd_cancelImageLoadOperationWithKey を実行して最後のタスクをキャンセルし、現在進行中の非同期ダウンロード操作がなく、今後の操作と競合しないことを確認し、
    プレースホルダー マップを設定します。
  3. SDWebImageManager、SDImageLoaderProgressBlock を初期化し、NSProgress、SDWebImageIndicator をリセットします。
  4. loadImageWithURL: のダウンロードを開始し、返された SDWebImageOperation をキーを validOperationKey にして sd_operationDictionary に保存します。
  5. 画像を取得した後、sd_setImage: を呼び出し、新しい画像にトランジション アニメーションを追加します。
    アニメーションが終了したらインジケーターを停止します。

バッファリングについて

SDWebImage は画像キャッシュのサポートを提供し、この関数は SDImageCache クラスを担当します。画像キャッシュは(Memory)メモリと(Disk)ハードディスクのデュアルキャッシュ機構に分かれています。ディスク キャッシュの書き込み操作は非同期であるため、UI 操作には影響しません。

メモリバッファ

メモリ バッファリングは主に NSCache オブジェクトを使用して実装されます。NSCache は、可変辞書と同様にキーと値のペアを格納する方法を提供するコレクションのようなコンテナーですが、可変辞書よりもキャッシュに適しています。

  1. 最も重要な理由は、NSCache がスレッド セーフであることです。NSMutableDictionary を使用してキャッシュをカスタマイズする場合は、ロックとロックの解放を考慮する必要があります。これは NSCache によってすでに行われています。
  2. 次に、メモリが不足すると、NSCache は手動介入なしで保存されているオブジェクトを自動的に解放しますが、カスタム実装の場合は、メモリの状態を監視してからオブジェクトをさらに削除する必要があります。
  3. もう 1 つのポイントは、NSCache のキーがコピーされないため、キーに NSCopying プロトコルを実装する必要がないことです。

ディスクバッファ

これは NSFileManager オブジェクトを使用して実装されます。写真が保存される場所は Cache フォルダー内です。さらに、SDImageCache は、画像を非同期で保存するためのシリアル キューも定義します。

SDImageCache は、イメージをキャッシュ、取得、削除、クリアするための多数のメソッドを提供します。各イメージについて、メモリまたはディスク内でこれらの操作を簡単に実行するには、イメージにインデックスを付けるためのキー値が必要です。メモリ内では NSCache のキー値として使用し、ディスク内ではこのキーをイメージのファイル名として使用します。リモート サーバーからダウンロードされた画像の場合、その URL がキーとして最適です。

バッファクリア戦略

ディスク (ハードディスク) キャッシュのクリーニング戦略: SDWebImage は、APP が終了するたびにクリーニング タスクを実行します。キャッシュをクリアするルールは 2 つのステップで実行されます。最初のステップは、期限切れのキャッシュ ファイルをクリアすることです。期限切れのキャッシュがクリアされると、スペースが不足します。次に、残りのスペースが要件を満たすまで、ファイル時間の早い順から遅い順に並べ替えを続け、キャッシュされた最も古いファイルを最初にクリアします。

おすすめ

転載: blog.csdn.net/m0_63852285/article/details/130692460