SDWebImage源码解析

SDWebImage库总体分为这么几个部分:

  1. 类似UIImageView+WebCache这样的面向使用者的接口,由各个分类的sd_setImageWithURL:...系列方法组成,是我们最常接触使用的部分。
  2. SDImageCache,是SDWebImage库中负责缓存工作的类。
  3. SDImageDownloader,是SDWebImage库中负责下载工作的类。
  4. SDWebImageManager,是管理SDImageCacheSDImageDownloader,让它们协同工作的类。在各个分类的背后默默工作。

UIView+WebCacheOperation

这个分类中提供了几个方法,可以对operation进行操作。

首先这个分类中,使用RuntimeUIView添加一个字典属性(NSMutableDictionary<NSString *, id>),里面存储了一些下载操作id <SDWebImageOperation> operation,可能为一个op或者op集合。

删除operation

- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key

查找字典中以key为键的op,并删除。

取消operation

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key

查找字典中以key为键的op,调用- (void)cancel方法。(如果是集合,则遍历调用cancel)。取消后会将op从字典中删除。

添加operation

- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key

先取消key对应的op,再将新想op添加到字典中。



UIView+WebCache

这个分类中提供了图片下载、设置的方法,为这个库中UIImageView分类和UIButton分类的sd_set...系列方法提供支持。还提供了显示/隐藏ActivityIndicator的方法。

- (nullable NSURL *)sd_imageURL;

获取当前视图图片的url,方法通过Runtime获取,因此如果我们在设置图片时直接使用setImage而非使用sd中的方法,会使这个方法获取的值错误。

// 取消当前视图的图片下载
- (void)sd_cancelCurrentImageLoad

上面这个方法会以类名为key,调用UIView+WebCacheOperation中的- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key方法来取消图片下载操作。

- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
    if (setImageBlock) {
        setImageBlock(image, imageData);
        return;
    }
    
#if SD_UIKIT || SD_MAC
    if ([self isKindOfClass:[UIImageView class]]) {
        UIImageView *imageView = (UIImageView *)self;
        imageView.image = image;
    }
#endif
    
#if SD_UIKIT
    if ([self isKindOfClass:[UIButton class]]) {
        UIButton *button = (UIButton *)self;
        [button setImage:image forState:UIControlStateNormal];
    }
#endif
}

内部方法设置图片,如果有setImageBlock则调用它来设置图片,否则的话UIImageView调用setImage:方法,UIButton调用setImage:forState:方法来设置图片。

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock;

这个方法是这个分类的核心方法,也是UIImageView+WebCacheUIButton+WebCache中各个方法设置图片时实际调用的方法。

这个方法中做的主要事情是调用SDWebImageManager中的方法创建一个operation,并将这个op添加到op字典中。

方法中会在创建operation前使用上面内部方法设置placeholder图片,如果设置了SDWebImageDelayPlaceholder,则会跳过,最后在operation回调中,如果下载图片失败再设置placeholder。

方法中对op的complete回调进行了处理,如果设置了SDWebImageAvoidAutoSetImage,会直接调用completeBlock,否则的话会使用下载的图片或者占位图作为参数,调用setImageBlock, 最后调用completeBlock

这两个block的调用都是通过调用上面提到的内部方法sd_setImage:imageData:basedOnClassOrViaCustomSetImageBlock:

实际在UIImageView+WebCache中调用这个方法时,setImageBlock是nil,除非调用的是带completeBlock的方法并设置了block值,否则completBlock也会传入nil,最终将使用imageView.image = image这样的方法设置图片,而不是由外部block设置。

UIButton+WebCache中调用时,setImageBlock会有值,其内容是调用UIButtonsetImage:forState:或者setBackgroundImage:forState:方法。completBlock除非设置否则为空。



UIImageView+WebCache

这个分类中提供了一系列sd_setImage...方法供外部调用,是使用者最经常接触的API,这系列方法最终都是调用到了UIView+WebCache中的核心方法。

另外,UIImageView是支持多图动画的,这个分类也提供了相应的方法:

- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs {
    [self sd_cancelCurrentAnimationImagesLoad];
    __weak __typeof(self)wself = self;

    NSMutableArray<id<SDWebImageOperation>> *operationsArray = [[NSMutableArray alloc] init];

    for (NSURL *logoImageURL in arrayOfURLs) {
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            if (!wself) return;
            dispatch_main_async_safe(^{
                __strong UIImageView *sself = wself;
                [sself stopAnimating];
                if (sself && image) {
                    NSMutableArray<UIImage *> *currentImages = [[sself animationImages] mutableCopy];
                    if (!currentImages) {
                        currentImages = [[NSMutableArray alloc] init];
                    }
                    [currentImages addObject:image];

                    sself.animationImages = currentImages;
                    [sself setNeedsLayout];
                }
                [sself startAnimating];
            });
        }];
        [operationsArray addObject:operation];
    }

    [self sd_setImageLoadOperation:[operationsArray copy] forKey:@"UIImageViewAnimationImages"];
}

- (void)sd_cancelCurrentAnimationImagesLoad {
    [self sd_cancelImageLoadOperationWithKey:@"UIImageViewAnimationImages"];
}

使用多个url来创建多个operation,再将这些op放入数组,最后将op数组添加到op字典中。添加op和取消下载都单独提供了一个key与正常的一张图片的操作区分。

UIImageView+HighlightedWebCache与此分类类似,唯一不同的是,它会提供核心方法参数中的setImageBlock,其内容就是设置highlightedImage

UIButton+HighlightedWebCache与此分类类似,唯一不同的是,它会提供核心方法参数中的setImageBlock,其内容就是设置setImage:forState:或者setBackgroundImage:forState:



UIImage+GIF

使用NSData创建Gif图片

+ (UIImage *)sd_animatedGIFWithData:(NSData *)data

判断是否是Gif

- (BOOL)isGIF {
    return (self.images != nil);
}


SDWebImagePrefetcher

预加载图片,默认在主队列,低优先级加载图片。

可以通过prefetcherQueueoptionsmaxConcurrentDownloads等对预加载进行控制,可以通过代理的回调获取预加载的状态信息。



SDWebImageOperation

一个协议,里面只定义了一个取消方法。

@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end


SDImageCacheConfig

图片缓存设置

  • shouldDecompressImages,是否解码图片。对下载和缓存的图片解码,可以加快图片的显示,但会消耗更多的内存。默认YES。
  • shouldDisableiCloud,是否禁用iCloud备份,默认YES,即禁用。SharedSDImageCache默认存储的沙盒路径(Cache)是不会被iCloud备份的,但是SDImageCache可以添加自定义缓存路径,这时候就可能存储在会被iCloud备份的路径上,此时这个选项就有了意义。
  • shouldCacheImagesInMemory,是否在内存中缓存图片,默认YES。
  • maxCacheAge,最大缓存时间(秒为单位),默认一周。
  • maxCacheSize,最大缓存空间(bytes),默认0。

SDImageCache

SDImageCache图片缓存,它包括内存缓存和可选的硬盘缓存,硬盘缓存写入时是异步的,不会阻塞UI。

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory

初始化,SD会在沙盒的Cache文件夹下创建命名空间同名的文件夹A,最终以/Sandbox/Library/Caches/namespace/com.hackemist.SDWebImageCache.namespace作为硬盘缓存路径。

初始化时会注册通知,在程序收到内存警告时清除所有内存缓存,在即将关闭和进入后台时清除过期硬盘缓存。删除过期文件方法中,会优先删除过期文件(默认为保存一周)。
过期文件删除之后计算缓存大小,如果大小超过设置的缓存大小限制,会继续删除,优先删除最旧的图片,删到缓存大小为设置大小一半为止。

会创建一个自定义串行队列做IO操作,所有的存入、删除、查询操作都会在这个队列里完成,以此来保证线程安全。获取图片并没有控制在此队列完成。

sharedImageCache单例会以default为命名空间初始化。

内部的AutoPurgeCache, 继承自NSCache,在收到内存警告时,会自动删除内存中的所有缓存。SD中的内存缓存属性为NSCache,实际为使用AutoPurgeCache初始化的实例。

NSUInteger SDCacheCostForImage(UIImage *image), 计算一张图片的缓存大小消耗。在Mac中为wid * hei, 在iOS中为wid * scale * hei * scale



- (void)addReadOnlyCachePath:(nonnull NSString *)path

添加硬盘缓存的搜索路径,可以在想预加载Bundle资源时使用。



- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key

以key获取硬盘缓存文件的文件名,文件名是将key做MD5加密,如果后后缀名加上文件后缀。



- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk

根据参数和配置(SDImageCacheConfig)将图片缓存到内存和硬盘。

缓存到硬盘时,会控制在初始化时创建的io队列进行操作,实际调用了

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key

这个方法进行存储,这个方法中会检查队列是否是指定的io队列,不会再控制代码在io队列上执行。

如果方法没提供imageData,会将image转为Data, 最后存入硬盘的是Data。

如果设置中包含了禁用iCloud备份,会在存入时对URL设置禁用iCloud备份。



- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock

以key获取文件路径,查询硬盘图片是否存在,并不会加载图片。



- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memCache objectForKey:key];
}

以key查找内存缓存的图片。



- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }

    return diskImage;
}

// inner method
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    // 查找硬盘缓存Data
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        UIImage *image = [UIImage sd_imageWithData:data];
        // 如果文件名包含@2x、@3x,会以不同的scale创建UIImage
        image = [self scaledImageForKey:key image:image];
        if (self.config.shouldDecompressImages) {
              // 解码图片
            image = [UIImage decodedImageWithImage:image];
        }
        return image;
    }
    else {
        return nil;
    }
}


获取硬盘缓存的图片,查找的时候会从所有的路径查找(默认路径+自定义路径),查找到Data后会根据图片种类创建不同的UIImage对象([UIImage sd_imageWithData:]), 比如Gif、WebP等,并根据文件名是否包含@2x、@3x调整image的scale。

如果设置了预解码,会对图片进行解码,解码操作会过滤调动图和含有alpha通道的图片。

如果设置了内存缓存,会将图片添加到内存缓存中。



- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key

查找缓存的图片,先从内存查找,没有再从硬盘查找。



- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key 
                                               done:(nullable SDCacheQueryCompletedBlock)doneBlock

返回一个[NSOperation new], 方法会先查找内存缓存,如果找到则以对应的图片、Data、缓存类型等调用done回调。如果没找到会在io队列查找硬盘缓存。

如果所返回的NSOperation被取消,则会停止查找硬盘缓存。



- (void)removeImageForKey:(nullable NSString *)key 
                 fromDisk:(BOOL)fromDisk 
           withCompletion:(nullable SDWebImageNoParamsBlock)completion

从缓存中删除图片。

如果设置了内存缓存,则会从内存缓存中删除图片。

如果fromDisk为真,则会在io队列中删除硬盘缓存。

回调会在主线程被调用。



- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock

删除过期文件方法中,会优先删除过期文件(默认为保存一周)。

过期文件删除之后计算缓存大小,如果大小超过设置的缓存大小限制,会继续删除,优先删除最旧的图片,删到缓存大小为设置大小一半为止。



SDWebImageDownloaderOperation

SDWebImageDownloaderOperation继承自NSOperation,实现了多个代理协议方法:

  • SDWebImageDownloaderOperationInterface, 提供创建对象方法;添加下载过程回调、结果回调;设置是否解码图片等。
  • SDWebImageOperation,提供- (void)cancel方法。
  • NSURLSessionTaskDelegateNSURLSessionDataDelegate

这个类是下载图片的operation对象,它会处理NSURLSessionTaskDelegateNSURLSessionDataDelegate中的方法。

这个类内部有两个NSURLSession类型属性:

  • unownedSession,外部提供的session,由外部管理和负责置空,代理和代理方法也由外部控制实现。
  • ownedSession,当外部提供的session为空时,内部创建的session,自己负责停用和置空,自己实现代理方法。

SD实际代码中,外部session由SDWebImageDownloader提供和管理,Downloader中实现了相关代理方法,但是在代理方法中会调用SDWebImageDownloaderOperation实现的相关代理方法来处理逻辑:

// SDWebImageDownloader中
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
    // 调用operation实现的代理方法
    [dataOperation URLSession:session task:task didCompleteWithError:error];
}

这个类内部维护了一个回调字典,存储了所有添加的progressCallbackcompletedCallback,并创建了一个barrierQueue,将对回调字典的操作放到这个队列中来保证线程安全。

类里面重写了NSOperation- (void)start- (void)cancel方法,结合下载的各个代理方法控制operation的finishedexecuting状态, 并根据NSOperation的文档重写了这两个属性的setter

@property (readonly, getter=isExecuting) BOOL executing

A Boolean value indicating whether the operation is currently executing.
The value of this property is YES if the operation is currently executing its main task or NO if it is not.

When implementing a concurrent operation object, you must override the implementation of this property so that you can return the execution state of your operation. 
In your custom implementation, you must generate KVO notifications for the isExecuting 
key path whenever the execution state of your operation object changes. For more information about manually generating KVO notifications, see Key-Value Observing Programming Guide.
You do not need to reimplement this property for nonconcurrent operations.

finished与之相同。

如果实现的operation是并发的,必须重写这两个属性,来自己控制op的状态,并且需要在属性改变时给出KVO通知。如果不是并发的,则没必要重写。

@implementation SDWebImageDownloaderOperation {
    @synthesize executing = _executing;
    @synthesize finished = _finished;
    
    - (void)setFinished:(BOOL)finished {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }

    - (void)setExecuting:(BOOL)executing {
        [self willChangeValueForKey:@"isExecuting"];
        _executing = executing;
        [self didChangeValueForKey:@"isExecuting"];
    }
}

重写父类属性时,需要使用到成员变量,则要用@synthesize,否则在子类重写的settergetter中是访问不到的。



SDWebImageDownloader

SD中的图片下载器,与SDWebImageCache构成这个库中的“下载”和“缓存”两大核心。

下载选项

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
     // 默认,影响下载操作在队列中的优先级
    SDWebImageDownloaderLowPriority = 1 << 0,
    // 渐进式下载,可以逐步显示出图片全部
    SDWebImageDownloaderProgressiveDownload = 1 << 1,

    /**
     * 默认情况下下载图片请求会设置NSURLRequestReloadIgnoringLocalCacheData缓存策略,不使用NSURLCache缓存。
     * 如果设置了这个选项,会使用NSURLRequestUseProtocolCachePolicy,启动NSURLCache缓存。
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    /**
     * 忽略从NSURLCache缓存的图片,如果图片是从NSURLCache获取到,则以nil来回调
     * 可以结合`SDWebImageDownloaderUseNSURLCache`一起使用。
     */
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    
    // 在App进入后台继续下载图片
    SDWebImageDownloaderContinueInBackground = 1 << 4,

    // 影响NSMutableURLRequest.HTTPShouldHandleCookies属性值
    SDWebImageDownloaderHandleCookies = 1 << 5,

    // 允许不被信任的SSL证书
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    // 高优先级下载图片
    SDWebImageDownloaderHighPriority = 1 << 7,
    
    // 默认情况下,硬盘缓存的Data是在请求中逐渐拼接起来的NSMutableData
    // 如果设置此项,则使用UIImagePNGRepresentation(image), 此Data会比前者更小
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};

下载顺序

typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    // 默认值,先进先出
    SDWebImageDownloaderFIFOExecutionOrder,


    // 后进先出
    SDWebImageDownloaderLIFOExecutionOrder
};


属性初始化

  • shouldDecompressImages, 是否解码图片,默认YES。
  • executionOrder, 队列顺序,默认先进先出。
  • maxConcurrentOperationCount,队列最大任务并发数,默认6个。
  • downloadTimeout,任务超时时间,默认15秒。

另外,会创建一个NSURLSession用来进行请求,注意,不是使用sharedSession,而是单独创建一个session。在Downloader对象销毁时会调用session的- (void)invalidateAndCancel方法并置空。

另外还会会维护一个URLOperations字段来存储所有的operation,这个字典的操作会放在一个自营第队列中,来保证线程安全。



下载图片

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock

使用URL创建请求request,根据options参数设置request的缓存策略(是否使用NSURLCache缓存)、设置cookie处理方式、设置请求头等。

如果URL对应的SDWebImageDownloaderOperation不存在于内部字典中,则会以request创建一个op,根据options参数设置op的优先级,并添加到队列中。如果队列的执行顺序是后进先出,则将之前的最后一个op添加依赖到新的op上来确保执行顺序。

方法会返回一个Token,可以用来取消对应的op。



SDWebImageCombinedOperation

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>

@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end

@implementation SDWebImageCombinedOperation

- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();
        
        // TODO: this is a temporary fix to #809.
        // Until we can figure the exact cause of the crash, going with the ivar instead of the setter
//        self.cancelBlock = nil;
        _cancelBlock = nil;
    }
}

这个类遵守SDWebImageOperation协议,实现了- (void)cancel方法,并且有一个NSOperation类型的cacheOperation属性和一个cancelBlock属性。

- (void)cancel方法中,cacheOperation的取消方法会被调用,然后调用cencelBlock



SDWebImageManager

SD中的核心类,由它来协调SDImageCacheSDWebImageDownloader协同工作。

下载选项:

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    // 下载失败过的URL会从黑名单中删除并重新尝试下载
    SDWebImageRetryFailed = 1 << 0,

    // 对应SDWebImageDownloaderLowPriority,低优先级的下载任务不会在UI交互时启动
    SDWebImageLowPriority = 1 << 1,

     // 只进行内存缓存
    SDWebImageCacheMemoryOnly = 1 << 2,

    // 对应SDWebImageDownloaderProgressiveDownload,渐进式下载显示
    SDWebImageProgressiveDownload = 1 << 3,

    /**
     * 对应SDWebImageDownloaderUseNSURLCache
     * 即使图片有SD缓存,也会遵循HTTP的缓存控制,在需要的时候从远端获取图片,比如远端图片有新版本。本地硬盘缓存会由NSURLCache进行处理,而不是SDWebImage。
     * 这个在同一个URL对应的图片可能发生改变时很有用,比如一个头像图片的URL。
     * 如果缓存图片被刷新了,回调会先被调用一次,参数是缓存图片,然后用请求的图片作为参数再调用一次。
     */
    SDWebImageRefreshCached = 1 << 4,

    // 对应SDWebImageDownloaderContinueInBackground,App进入后台时继续下载图片,如果后台任务超时,任务会被取消。
    SDWebImageContinueInBackground = 1 << 5,

    /**
     * 对应SDWebImageDownloaderHandleCookies,会设置
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     * 来处理存储在NSHTTPCookieStore中的cookie
     */
    SDWebImageHandleCookies = 1 << 6, 会设置

    // 对应SDWebImageDownloaderAllowInvalidSSLCertificates,允许不受信任的SSL证书
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    // 对应SDWebImageDownloaderHighPriority,高优先级任务会在下载队列中被优先下载
    SDWebImageHighPriority = 1 << 8,
    
    // 延迟占位图展示,默认占位图会在下载图片前显示,使用此项后,占位图会在下载任务完成后没有图片的情况下才显示
    SDWebImageDelayPlaceholder = 1 << 9,

    /** 默认SDWebImageManager是不会调用下面的代理方法预处理下载好的动图的:
     * imageManager:transformDownloadedImage:withURL:
     * 使用这个选项,manager在下载完成后、缓存图片和回调前会调用此代理方法获取处理过的图片
     */
    SDWebImageTransformAnimatedImage = 1 << 10,
    
    // 默认会自动将图片设置到视图上,使用此项取消自动设置
    SDWebImageAvoidAutoSetImage = 1 << 11,
    
    // 对应SDWebImageDownloaderScaleDownLargeImages,如果设置了`SDWebImageProgressiveDownload`,则此项会失效
    SDWebImageScaleDownLargeImages = 1 << 12
};


协议方法

@class SDWebImageManager;

@protocol SDWebImageManagerDelegate <NSObject>

@optional

// 控制图片是否应该下载
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;


// 在图片下载后、缓存图片前调用此方法处理图片,方法会在全局队列中调用
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

@end


- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;

cacheKeyForURL:返回URL对应的缓存key,如果有cacheKeyFilter会使用它的返回值作为最终的值,否则返回url.absoluteString



- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;

如果URL对应的图片不在缓存中,则进行下载。

方法会创建并返回一个SDWebImageCombinedOperation类型的operation,operation的cacheOperation是由[self.imageCache queryCacheOperationForKey:...]方法返回的一个NSoperation

如果需要下载图片,则会调用[self.imageDownloader downloadImageWithURL:...]方法下载图片,方法返回一个SDWebImageDownloadToken类型的token,这个token会被前面的operation的cancelBlock捕获,在block中使用这个token来调用[self.imageDownloader cancel:token]方法进行取消。



- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url

将图片缓存到内存和硬盘。



- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

异步确认url对应的图片是否存在于缓存中,先从内存查找,否则异步在硬盘查找,回调在主线程执行。



- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

异步确认URL对应的图片是否存在于硬盘缓存, 不会加载图片,回调在主线程执行。



各个Operation之间的关系

在上面的各个类中,可以看到有好几种Operation,和取消方法。总结如下:

NSOperation
1. - (void)cencel;

SDWebImageOperation协议
1. - (void)cancel;

SDImageCache:
`queryCacheOperationForKey:...`方法
1. query操作首先查找内存, 命中则回调,返回nil
2. 返回NSOperation,异步查找硬盘,回调。

SDWebImageDownloaderOperation:
1. 内部维护callbackBlocks数组,数组元素为Dic<KEY,BLOCK>
2. `- (void)cancel`,调用父类NSOperation的cancel方法,调用dataTask的cancel方法,设置状态
3. `- (BOOL)cancel:(nullable id)token`,从内部维护的callbackBlocks数组中删除对应的回调,调用`- (void)cancel`方法

SDWebImageDownloader:
1. 内部维护URLOperations字典,key为URL,值为SDWebImageDownloaderOperation
2. `- (void)cancel:(nullable SDWebImageDownloadToken *)token`,
    使用token.URL从URLOperations字典中获取downloaderOp, 调用downloaderOp的
    `- (BOOL)cancel:(nullable id)token`方法。将downloaderOp从字典中删除。
3. `downloadImageWithURL:..`方法返回SDWebImageDownloadToken,
    方法内会创建SDWebImageDownloaderOperation,将downloaderOp添加到URLOperations字典中,将downloaderOp添加到队列中。
4. `- (void)cancelAllDownloads`,方法会调用`[self.downloadQueue cancelAllOperations]`,系统会对队列内的所有operation调用`- (void)cancel`方法。

SDWebImageCombinedOperation:
1. 遵守SDWebImageOperation协议
2. 有一个NSOperation类型cacheOperation属性
3. 有一个cancelBlock属性
4. `- (void)cancel`方法内会调用cancelBlock和cacheOperation的cancel方法

SDWebImageManager
1. 内部维护一个runningOperations数组,里面的对象是SDWebImageCombinedOperation
2. `loadImageWithURL:..`,会创建`SDWebImageCombinedOperation`类型的combinedOp, 
    创建combinedOp.cacheOperation为`[SDImageCache.sharedCache queryCacheOperationForKey:...]`的返回值,
    如果没有查找到缓存图片,则下载。下载会调用`[SDWebImageDownloader.sharedDownloader downloadImageWithURL:...]`方法,
    返回的SDWebImageDownloadToken被combinedOp的cancelBlock捕获。
3. 在queryCache或者downloadImage的过程中,如果combinedOp被cancel,则会被从runningOperations中删除。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容