[iOS] Aprendizaje del código fuente de SDWebImage: no terminado

Las funciones principales y puntos de conocimiento relacionados de SDWebImage.

SDWebImage es una popular biblioteca de terceros para la descarga asincrónica y el almacenamiento en caché de imágenes en aplicaciones iOS y macOS. Proporciona una manera simple pero poderosa de manejar la carga y el almacenamiento en caché de imágenes de red, con las siguientes características clave:

  1. Descarga asincrónica: SDWebImage utiliza un mecanismo de subprocesos múltiples que permite descargar imágenes de forma asincrónica en segundo plano para evitar bloquear la interfaz de usuario de la aplicación.
  2. Almacenamiento en caché de imágenes: tiene mecanismos de almacenamiento en caché de memoria y de disco que pueden guardar automáticamente las imágenes descargadas en la memoria y el disco. De esta manera, en cargas posteriores, puede recuperar rápidamente la imagen del caché sin tener que descargarla nuevamente.
  3. Imágenes de marcador de posición y carga progresiva: SDWebImage admite la visualización de imágenes de marcador de posición durante la descarga de imágenes, así como la carga progresiva de imágenes, lo que permite a los usuarios ver el progreso de carga de la imagen paso a paso.
  4. Limpieza de caché: SDWebImage también ofrece la opción de limpiar el caché; puede limpiar manualmente los cachés que están vencidos o que ya no son necesarios según sea necesario.

Clases de herramientas y sus funciones.

  • NSData+ImageContentType determina el formato de la imagen actual a través de datos de imagen
  • El caché SDImageCache define el caché de segundo nivel (NSCache) de memoria y disco y es responsable de administrar el singleton de caché.
  • SDWebImageCompat Definición de macros y escalado de imágenes en línea para garantizar la compatibilidad con diferentes plataformas/versiones/pantallas, etc.
  • Descompresión de imágenes SDWebImageDecoder, solo hay una interfaz interna
  • SDWebImageDownloader administra descargas de imágenes asíncronas, administra colas de descarga, administra operaciones, administra resultados de procesamiento de solicitudes de red y excepciones únicas, y almacena el
    bloque para devoluciones de llamadas de solicitudes de red. La estructura de datos que entiendo es probablemente
    // estructura {"url":[{"progress ":"progressBlock ”},{“completo”:“completeBlock”}]}
  • SDWebImageDownloaderOperation implementa NSOperation para la descarga asincrónica de imágenes. La solicitud de red se proporciona al agente NSURLSession para descargar un
    objeto de tarea de operación personalizado. Debe implementar manualmente la cancelación de inicio y otros métodos.
  • La clase de administración central SDWebImageManager encapsula principalmente la administración de caché + administración de descargas. La interfaz principal downloadImageWithURL es de interés único.
  • El protocolo de operación SDWebImageOperation solo define el proxy de downloaderOperation en la interfaz de cancelación de operación.
  • SDWebImagePrefetcher descarga previamente imágenes con baja prioridad y rara vez se usa para encapsular simplemente SDWebImageViewManager.
  • MKAnnotationView+WebCache: carga imágenes de forma asincrónica para MKAnnotationView
  • UIButton+WebCache carga imágenes de forma asincrónica para UIButton
  • UIImage+GIF convierte datos de imagen en una imagen de formato específico
  • UIImage+MultiFormat convierte datos de imagen en una imagen de formato específico
  • UIImageView+HighlightedWebCache carga imágenes de forma asincrónica para UIImageView
  • UIImageView+WebCache carga imágenes de forma asincrónica para UIImageView
  • UIView+WebCacheOperation guarda las operaciones actuales de MKAnnotationView/UIButton/UIImageView para la descarga asincrónica de imágenes.

Proceso de descarga

Proceso de uso básico

Insertar descripción de la imagen aquí

Proceso de implementación

  1. SDWebImage primero verificará si la imagen solicitada existe en el caché (incluido el caché de memoria y el caché de disco). Si la imagen se encuentra en el caché, se cargará desde el caché inmediatamente para proporcionar un acceso más rápido.
  2. Si la imagen no se encuentra en el caché, SDWebImage inicia una tarea de descarga asincrónica para descargar la imagen desde la URL proporcionada. Maneja solicitudes de red y descargas de imágenes en segundo plano sin bloquear la interfaz de usuario.
  3. Durante la descarga de la imagen, SDWebImage puede mostrar la imagen de marcador de posición especificada (si se proporciona) en UIImageView.
  4. Una vez descargada la imagen, SDWebImage la cargará en UIImageView y manejará automáticamente el caché para que la imagen se pueda obtener rápidamente en futuras solicitudes.

Análisis de código fuente

Sigamos el proceso de llamada paso a paso.

Llamar 1

Llame a la serie de métodos sd_setImageWithURL en UIImageView+WebCache:

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

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

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

- (void)sd_setImageWithURL:(nullable NSURL *)url 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];
}

Estos métodos finalmente llaman a sus métodos universales:

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

Llamada 2

El método multiuso anterior en realidad llama a un método de la clase 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;
}

Primero está el nombre del método:

Tiene un total de cinco parámetros. La URL es el enlace de la imagen en línea que necesitamos descargar. La imagen de marcador de posición es del tipo UIImage. Para SDWebImageOptions, vemos su código fuente y consultamos información relacionada. Es un método de selección A expuesto y disponible. para que los usuarios lo utilicen.

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. contexto

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;

para

[self sd_cancelImageLoadOperationWithKey:validOperationKey];

Su código fuente:

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

Este código está en UIView+WebCacheOperationEsta clase es la principal responsable de la operación de descarga. Mantiene un diccionario de diccionario de operaciones en la memoria para cada objeto UIKit que utiliza objetos asociados. Puede agregar las operaciones de descarga correspondientes a diferentes valores de clave, o puede cancelar la operación según la clave cuando la operación de descarga no se completa.

La clave del diccionario de operación es generalmente el nombre de la clase. De esta manera, si se llama al mismo UIImageView dos veces al mismo tiempo, la primera operación de descarga se cancelará primero y luego la operación en el diccionario de operaciones se asignará a la segunda operación de descarga.

Entonces echemos un vistazo al código fuente de 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;
    }
}

Devuelve un objeto asociado por valor clave. Si las operaciones están vacías, cree un objeto y vincúlelo al valor clave.

Si se ingresan dos valores clave idénticos, los objetos asociados obtenidos serán los mismos. La clave entrante se busca para ver si ya existe en el diccionario devuelto. Si existe, se cancelan todas las operaciones. Si el método conformsToProtocol se ajusta a este protocolo (el método de cancelación se declara en el protocolo), también llama al método de cancelación. en el protocolo.

beneficio:

  • Debido a que está dirigido a UIImageView, cancelar la operación anterior ahorra tiempo y tráfico.
  • Evite la reutilización de SDWebImage. Esto es para evitar la descarga repetida de una imagen. Una vez completada la carga de la imagen, la devolución de llamada primero verificará si la operación de la tarea aún está allí. Si no está allí, la devolución de llamada no se mostrará. De lo contrario, se mostrará la devolución de llamada y se eliminará la operación.
  • Cuando una interrupción del programa hace que el enlace falle, la descarga actual todavía está en la cola de operaciones, pero debería estar en un estado no válido. Podemos asegurarnos de que esta situación no exista para el enlace en la cola de operaciones cancelando primero el enlace actualmente descarga en curso.

2. Crea un administrador de imágenes.

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

Si no se ha creado, utilice un singleton para crearlo; si se ha creado, elimine el administrador en el contexto para evitar un bucle de retención.

3. Establecer imagen de marcador de posición

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

Este código se utiliza principalmente para configurar imágenes de marcador de posición de acuerdo con condiciones específicas y activar una lógica de sincronización de caché débil cuando sea necesario. Tenga en cuenta que este código puede cambiar o eliminarse a medida que la biblioteca se actualiza y refactoriza.

4. Determinar la URL

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

Determine si la URL entrante está vacía. De lo contrario, obtenga el progreso de carga de la imagen y restablezcala a 0.

5. Cargando indicador de imagen

// 代码片段中的条件编译指令#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);
            }
        };

Este fragmento de código se utiliza en la biblioteca SDWebImage para manejar las actualizaciones de progreso durante la carga de imágenes.

6. Crear operación SDWebImage

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

Llame - (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nonnull SDInternalCompletionBlock)completedBlockal método para crear una tarea de descarga.

6.1 Eche un vistazo al código fuente para crear la tarea de descarga anterior

- (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 El método anterior iniciará la búsqueda en caché después de la verificación de la URL.

Método de llamada:- (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];
    }
}

Para simplificarlo, es:

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

6.3 Función de consulta:

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

Este método realiza un procesamiento diferente según la cantidad de cachés:

  1. Si la cantidad de caché es 0, se devolverá vacío directamente;
  2. Si el número de cachés es 1, se llama directamente al método del primer objeto de caché queryImageForKey:options:context:cacheType:completion:y se le pasan los parámetros de forma transparente.
  3. Si el número de cachés es mayor que 1, se realizará un procesamiento diferente de acuerdo con la política de operación de consulta establecida queryOperationPolicy:
    • Si la política es SDImageCachesManagerOperationPolicyHighestOnly, se selecciona el último objeto de caché para la consulta.
    • Si la política es SDImageCachesManagerOperationPolicyLowestOnly, se selecciona el primer objeto de caché para la consulta.
    • Si la política es SDImageCachesManagerOperationPolicyConcurrent, cree un objeto SDImageCachesManagerOperation como operación de consulta y recorra los objetos de caché uno por uno para realizar consultas simultáneas.
    • Si la política es SDImageCachesManagerOperationPolicySerial, cree un objeto SDImageCachesManagerOperation como operación de consulta y recorra los objetos de caché uno por uno para realizar consultas en serie.

6.4 La función anterior llamará además a la función del mismo nombre de la clase SDImageCache

Código fuente:

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

Este código establece las opciones de caché correspondientes cacheOptions de acuerdo con las opciones pasadas y luego llama al queryCacheOperationForKey:options:context:cacheType:donemétodo: para realizar la operación de consulta de caché y devuelve el objeto de la operación de consulta.

Opciones de caché Las opciones de caché incluyen:

  • SDWebImageQueryMemoryData: Consulta datos de la memoria caché.
  • SDWebImageQueryMemoryDataSync: consulta sincrónicamente datos almacenados en memoria caché.
  • SDWebImageQueryDiskDataSync: consulta los datos almacenados en caché del disco de forma sincrónica.
  • SDWebImageScaleDownLargeImages: Reduce el tamaño de imágenes grandes.
  • SDWebImageAvoidDecodeImage: evita decodificar imágenes.
  • SDWebImageDecodeFirstFrameOnly: solo decodifica el primer fotograma de la imagen animada.
  • SDWebImagePreloadAllFrames: precarga todos los fotogramas de imágenes animadas.
  • SDWebImageMatchAnimatedImageClass: clase que coincide con imágenes animadas.

6.5 Llame a la función que realmente realiza la operación de consulta de caché

Código fuente:

- (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. Búsqueda de memoria caché, imageFromMemoryCacheForKey:cómo usarla
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    
    
    return [self.memoryCache objectForKey:key];
}

Aquí, busca directamente en el caché a través de la clave. Si se encuentra aquí y no hay instrucciones forzadas para consultar el disco, volverá a llamar directamente.

    BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
    
    
        if (doneBlock) {
    
    
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
  1. Búsqueda de caché de disco, setObject:forKey:cost:cómo usarla

Si no se encuentra en el paso anterior, ingresará a la etapa de búsqueda de disco, en este momento se creará una nueva NSOperation y se ejecutará el bloque de código queryDiskBlock.

  • Encuentre el disco por clave y obtenga diskData
  • Si la imagen se encuentra en la memoria en el paso anterior, se asigna directamente; de ​​lo contrario, los datos del disco se decodifican y la imagen del disco se escribe en la memoria.
[self.memoryCache setObject:diskImage forKey:key cost:cost];

6.6 Llamar al método de descarga

Se llama al método de descarga cuando no se encuentran todas las búsquedas.

Primero, queryImageForKey:options:context: cacheType:completion:determine si se requiere la descarga en el bloque de devolución de llamada:

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

Si es necesaria la descarga, llame al callDownloadProcessForOperation de SDWebImageDownloader.

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

Este código determina si la imagen debe descargarse en función de los parámetros pasados ​​y llama al cargador de imágenes para descargar la imagen según la situación.

Si necesita descargar una imagen, primero determine si hay una imagen almacenada en caché cachedImage y la opción incluye SDWebImageRefreshCached. Si se cumplen las condiciones, primero notifique al bloque de finalización sobre la imagen almacenada en caché, luego pase la imagen almacenada en caché al cargador de imágenes, agregue el par clave-valor SDWebImageContextLoaderCachedImage en el contexto contextual y pase la imagen almacenada en caché al cargador de imágenes para compararla. Luego, llame al requestImageWithURL:options:context:progress:completed:método del cargador de imágenes, pase la dirección URL, las opciones, el contexto, el bloque de progreso ProgressBlock y el bloque de finalización completeBlock para realizar la operación de descarga de imágenes.

Sobre requestImageWithURL:options:context:progress:completed:el método:

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

Este código obtiene la imagen almacenada en caché cachedImage según el contexto y luego establece las opciones del descargador de imágenes downloaderOptions según las opciones entrantes y otras condiciones.

Las opciones de downloaderOptions incluyen:

  • SDWebImageLowPriority: descarga de baja prioridad.
  • SDWebImageProgressiveLoad: carga progresiva.
  • SDWebImageRefreshCached: fuerza la actualización de la caché.
  • SDWebImageContinueInBackground: continúa descargando en segundo plano.
  • SDWebImageHandleCookies: Maneja las cookies.
  • SDWebImageAllowInvalidSSLCertificates: permite certificados SSL no válidos.
  • SDWebImageHighPriority: descarga de alta prioridad.
  • SDWebImageScaleDownLargeImages: escala imágenes grandes descargadas.
  • SDWebImageAvoidDecodeImage: evita decodificar imágenes.
  • SDWebImageDecodeFirstFrameOnly: solo decodifica el primer fotograma.
  • SDWebImagePreloadAllFrames: precarga todos los fotogramas.
  • SDWebImageMatchAnimatedImageClass: clase que coincide con imágenes animadas.

Si existe una imagen almacenada en caché cachedImage y las opciones incluyen SDWebImageRefreshCached, desactive el indicador de carga progresiva y el indicador para leer imágenes de NSURLCache en las opciones downloaderOptions.

Finalmente, llame al downloadImageWithURL:options:context:progress:completed:método de descarga de imágenes para realizar la operación de descarga de imágenes.

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

Este código crea u obtiene una operación de descarga de imágenes basada en los parámetros pasados ​​y realiza la configuración y el procesamiento correspondientes.

  1. Compruebe si la dirección URL es nula. Si es nulo, se llama inmediatamente al bloque de finalización completeBlock, se devuelve un objeto de error y no hay imagen ni datos.
  2. Bloquee _ OperationsLock y obtenga la operación de descarga del diccionario self.URLOperations de acuerdo con la dirección URL. Si la operación de descarga no existe o se ha completado o cancelado, se crea una nueva operación de descarga y se agrega al diccionario self.URLOperations. Al mismo tiempo, configure la devolución de llamada después de que se complete la operación, incluida la eliminación de la entrada correspondiente en self.URLOperations. Agregue ProgressBlock y completeBlock a la operación y devuelva un token de cancelación downloadOperationCancelToken. Finalmente, agregue la operación a la cola de descarga downloadQueue.
  3. Si la operación de descarga ya existe, bloquee la operación y agregue el bloque de progreso ProgressBlock y el bloque de finalización completeBlock a la operación. Si la operación no se ejecuta, la prioridad de la cola de la operación se establece según las opciones. Luego desbloquee _operacionesLock.
  4. Finalmente, cree un token de objeto SDWebImageDownloadToken y configure la operación de operación, la dirección URL, la operación del objeto de solicitud.request y el token de cancelación downloadOperationCancelToken en el token. Finalmente devuelve el token.

Este código crea una operación de descarga y llama createDownloaderOperationWithUrl:options:context:a un método. Finalmente, echemos un vistazo a este método.

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

En general, este código crea un objeto de acción para descargar la imagen según la URL, las opciones y el contexto pasados. Establece el tiempo de espera de la solicitud, la política de caché y otras propiedades, al mismo tiempo que establece las credenciales de la operación, la prioridad de la cola, etc. según la información de la configuración. También se pueden aplicar modificadores y descifradores para solicitudes y respuestas mediante opciones en el contexto. Finalmente, el objeto de operación creado se devuelve para su uso posterior.

Este código es una de las lógicas centrales de la descarga de imágenes en la biblioteca SDWebImage y se utiliza para manejar la creación y configuración de las operaciones de descarga.

Luego use NSURLSession para descargar, de modo que haya un método proxy NSURLSession en SDWebImageDownloader.

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

Todos estos métodos llaman a (NSOperation *) OperationWithTask: (NSURLSessionTask *) tarea.
Obtenga la instancia SDWebImageDownloaderOperation correspondiente a través de la tarea y coloque las operaciones específicas en esta clase. En esta clase, realizamos operaciones de decodificación en las imágenes que deben decodificarse y determinamos si las imágenes deben escalarse.

6.6 Almacenamiento en caché de imágenes

Vuelva a llamar al requestImageWithURL:options:context:progress:completed:bloque de finalización de la función. Si la descarga se realiza correctamente, llame callStoreCacheProcessForOperation:url:options:context:downloadedImage:downloadedData:cacheType:finished:completeda la función para almacenar en caché el progreso.

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

Esta función realiza algunas configuraciones en el caché de imágenes y es el principal responsable de la función de caché storeImage:imageData:forKey:imageCache:cacheType:waitStoreCache:completion:. Echemos un vistazo a esta función:

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

Descubrí que esta función simplemente verifica si debe esperar a que se complete el almacenamiento en caché y llama storeImage:imageData:orKey:cacheType:completion:a la función. Sigamos leyendo:

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

Como puede ver, esta función establece algunas opciones para el método de caché y llama a la función de caché real storeImage:imageData:forKey:toMemory:toDisk:completion:.

Finalmente veamos esta función:

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

Puedes ver que self.memCache se almacena directamente en la memoria.

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

Si necesita guardarlo en el disco, llame al método en la cola asíncrona self.ioQueue para escribir en el disco.
Todos los cachés tienen límites de tiempo y de tamaño. Hay una serie de estrategias de limpieza en SDImageCache. El período de caché efectivo predeterminado de la imagen es de 7 días:

  1. Limpieza de memoria
[self.memCache removeAllObjects];
  1. limpieza de disco

SDWebImage monitorea la notificación de la aplicación que ingresa al fondo. Al ingresar al fondo, si la cantidad de caché actual es mayor o igual a la cantidad máxima de caché establecida, el contenido general se eliminará recursivamente en orden cronológico hasta que la cantidad de caché cumpla con las condiciones.

7 Descargar imágenes

Operaciones de descarga de imágenes:

[self sd_setImageLoadOperation:operation forKey:validOperationKey];

Eche un vistazo a su implementación:

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

Todas las operaciones se mantienen mediante un diccionario de diccionario de operaciones. Antes de ejecutar nuevas operaciones, cancele todas las operaciones.

Resumir

Este método implica muchos métodos, hablemos brevemente del proceso:

  1. Copie y convierta SDWebImageContext a mutable y obtenga el valor validOperationKey como identificación de verificación. El valor predeterminado es el nombre de clase de la vista actual;
  2. Ejecute sd_cancelImageLoadOperationWithKey para cancelar la última tarea y garantizar que no haya ninguna operación de descarga asincrónica actualmente en curso y que no entre en conflicto con la próxima operación;
    configure el mapa de marcador de posición.
  3. Inicialice SDWebImageManager, SDImageLoaderProgressBlock, restablezca NSProgress, SDWebImageIndicator;
  4. Comience a descargar loadImageWithURL: y almacene la SDWebImageOperation devuelta en sd_ OperationDictionary, siendo la clave validOperationKey;
  5. Después de obtener la imagen, llame a sd_setImage: y agregue una animación de transición a la nueva imagen;
    detenga el indicador después de que finalice la animación.

Acerca del almacenamiento en búfer

SDWebImage proporciona soporte para el almacenamiento en caché de imágenes y esta función es responsable de la clase SDImageCache. La caché de imágenes se divide en memoria (Memoria) y mecanismo de caché dual del disco duro (Disco). La operación de escritura del caché del disco es asíncrona, por lo que no afectará la operación de la interfaz de usuario.

buffer de memoria

El almacenamiento en búfer de memoria se implementa principalmente mediante objetos NSCache. NSCache es un contenedor similar a una colección que proporciona una forma de almacenar pares clave-valor similar a un diccionario mutable, pero es más adecuado para el almacenamiento en caché que un diccionario mutable.

  1. La razón más importante es que NSCache es seguro para subprocesos. Cuando utilice NSMutableDictionary para personalizar el caché, debe considerar bloquear y liberar bloqueos. NSCache ya lo ha hecho por nosotros.
  2. En segundo lugar, cuando no hay memoria suficiente, NSCache liberará automáticamente los objetos almacenados sin intervención manual. Si se trata de una implementación personalizada, necesita monitorear el estado de la memoria y luego eliminar más los objetos.
  3. Otro punto es que la clave de NSCache no se copiará, por lo que no es necesario implementar el protocolo NSCopying.

buffer de disco

Esto se implementa utilizando el objeto NSFileManager. La ubicación donde se almacenan las imágenes es la carpeta Caché. Además, SDImageCache también define una cola en serie para almacenar imágenes de forma asincrónica.

SDImageCache proporciona una gran cantidad de métodos para almacenar en caché, obtener, eliminar y borrar imágenes. Para cada imagen, para poder realizar fácilmente estas operaciones en ella en la memoria o en el disco, necesitamos un valor clave para indexarla. En la memoria, la usamos como valor clave de NSCache, y en el disco, usamos esta clave como el nombre de archivo de la imagen. Para una imagen descargada desde un servidor remoto, su URL es la mejor opción como clave.

Estrategia de limpieza del buffer

Estrategia de limpieza de caché del disco (disco duro): SDWebImage realizará tareas de limpieza cada vez que finalice la APLICACIÓN. Las reglas para borrar el caché se realizan en dos pasos. El primer paso es borrar los archivos de caché caducados. Si se borra el caché caducado, el espacio no es suficiente. Luego continúe ordenando por tiempo de archivo desde el principio hasta el último, borrando primero los archivos almacenados en caché más antiguos hasta que el espacio restante cumpla con los requisitos.

Supongo que te gusta

Origin blog.csdn.net/m0_63852285/article/details/130692460
Recomendado
Clasificación