深入理解SDWebImage(二)

作者: 文艺女青年的男人 | 来源:发表于2019-08-09 14:54 被阅读18次
解读SDWebImage
按照SDWebImage中给到的主流程,先进入UIImageView+WebCache,在进入UIView+WebCache,这两个步骤已经在深入理解SDWebImage(一)分析了,接下来主要分析一下SD中的核心 - SDWebImageManager。

核心方法

/**
 * 通过URL加载图片,如果cache中存在就从cache中获取,否则开始下载
 *
 * @param url            传入的image的url
 * @param options        获取图片的方式
 * @param context        获取
 * @param progressBlock  获得图片的进度(注意是在子队列中)
 * @param completedBlock  完成获取之后的回掉block
 * @return  返回一个SDWebImageCombinedOperation对象,用于表示当前的图片获取任务,在这个对象中可以取消获取图片任务
 */
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                                 completed:(nonnull SDInternalCompletionBlock)completedBlock;

其中,SDWebImageContext *是在5.0中新引入的参数,可以灵活的自定义高级功能.SDWebImageContext/SDWebImageMutableContext 是以以 SDWebImageContextOption为key、id(指定类型或者协议)为value 的NSDictionary/NSMutableDictionary

typedef NSDictionary<SDWebImageContextOption, id> SDWebImageContext;
typedef NSMutableDictionary<SDWebImageContextOption, id> SDWebImageMutableContext;

并且其中的SDWebImageContextOption是一个可扩展的String枚举类型,确实是很好的代码,之前都没这么用过,我们看一下这个枚举中都有什么类型,从网上查询到的一个表格,感觉总结的很全面,

Key Value Define
SDWebImageContextSetImageOperationKey NSString SDWebImageDefine.m
SDWebImageContextCustomManager SDWebImageManager SDWebImageDefine.m
SDWebImageContextImageTransformer id<SDImageTransformer> SDWebImageDefine.m
SDWebImageContextImageScaleFactor CGFloat SDWebImageDefine.m
SDWebImageContextStoreCacheType SDImageCacheType SDWebImageDefine.m
SDWebImageContextDownloadRequestModifier id<SDWebImageDownloaderRequestModifier> SDWebImageDefine.m
SDWebImageContextCacheKeyFilter id<SDWebImageCacheKeyFilter> SDWebImageDefine.m
SDWebImageContextCacheSerializer id<SDWebImageCacheSerializer> SDWebImageDefine.m
SDWebImageContextLoaderCachedImage UIImage/NSImage<SDAnimatedImage> SDImageLoader.m

(1).SDWebImageContextSetImageOperationKey - 其实是用来保存当前图片的获取任务服务的;
在当前UIView+WebCacheOperation中存在一个NSMapTable关联对象,保存多个图片的加载任务,通过当前的key可以获取到保存在NSMapTable中的加载任务,执行后续的cancel/remove操作

(2).SDWebImageContextCustomManager - 自定义的SDWebImageManager,默认使用[SDWebImageManager sharedManager]

(3). SDWebImageContextImageTransformer - 处理加载出来的图片,比如翻转圆角等

(4). SDWebImageContextImageScaleFactor - 当图片解压缩完之后的图片放大比例

(5). SDWebImageContextStoreCacheType - 图片的缓存规则

(6). SDWebImageContextDownloadRequestModifier - 可以用于在加载图片前修改NSURLRequest

(7). SDWebImageContextCacheKeyFilter - 指定图片的缓存key

(8). SDWebImageContextCacheSerializer - 转换需要缓存的图片格式

(9). SDWebImageContextLoaderCachedImage - 这个值定义在SDImageLoader.m中,传入一个UIImage的缓存对象,一般用不到.

这些contextOption可以灵活运用,实现我们想要的某些功能.

SDWebImageCombinedOperation

当前类遵循<SDWebImageOperation>协议,在当前类中,可以拿到cache Operation或者是load Operation任务,然后取消任务.通过上面的SDWebImageContextSetImageOperationKey,可以获取到NSMapTable的value,该value就是SDWebImageCombinedOperation类型

/**
 取消当前的operation任务
 */
- (void)cancel;

/**
 表示当前的图片是从缓存中查找到的,将从缓存中获取任务放到cacheOperation属性中
 */
@property (strong, nonatomic, nullable, readonly) id<SDWebImageOperation> cacheOperation;

/**
 当前的图片需要下载,将下载任务放入loaderOperation属性中
 */
@property (strong, nonatomic, nullable, readonly) id<SDWebImageOperation> loaderOperation;

其中cacheOperation:当执行完从cache中国获取任务,会回调SDWebImageOperation当前的缓存任务
loaderOperation:当执行下载任务,会返回当前的下载任务Operation,用于后续的取消操作

//SDWebImageCombinedOperation
//当前对象中取消当前的下载任务
- (void)cancel {
    @synchronized(self) {
        if (self.isCancelled) {
            return;
        }
        self.cancelled = YES;
        //是从缓存中读取数据 SDImageCache
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        //下载中读取数据 SDWebImageDownloadToken
        //取消NSOperation中的任务
        //取消SDWebImageDownloaderOperation中的自定义回调任务
        if (self.loaderOperation) {
            [self.loaderOperation cancel];
            self.loaderOperation = nil;
        }
        //从当前正在执行的operation列表中移除当前的SDWebImageCombinedOperation任务
        [self.manager safelyRemoveOperationFromRunning:self];
    }
}

SDWebImageManager

SDWebImageManager是当前SDWebImage的核心,通过当前的单例对象,可以合理的安排图片获取任务,以及图片的获取逻辑,看一看其.h中暴露了什么

/**
 当前SDWebImageManagerDelegate代理方法,默认是nil
 */
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;

/**
 图片缓存,被用来去查询图片的缓存,默认是SDImageCache
 */
@property (strong, nonatomic, readonly, nonnull) id<SDImageCache> imageCache;

/**
 下载图片,被用来去下载图片,默认是SDWebImageDownloader
 */
@property (strong, nonatomic, readonly, nonnull) id<SDImageLoader> imageLoader;

/**
 被用来处理下载下来的图片,如果想要设置可以在UIView+webCache中的context中设置SDWebImageContextImageTransformer,来决定transform,包括去圆角/翻转等
 */
@property (strong, nonatomic, nullable) id<SDImageTransformer> transformer;

/**
 图片的缓存key默认是url,通过设置该选项重新指定图片的缓存key,相关实现代码
* @code
 SDWebImageManager.sharedManager.cacheKeyFilter =[SDWebImageCacheKeyFilter cacheKeyFilterWithBlock:^NSString * _Nullable(NSURL * _Nonnull url) {
    url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
    return [url absoluteString];
 }];
 */
@property (nonatomic, strong, nullable) id<SDWebImageCacheKeyFilter> cacheKeyFilter;

/**
 * The default value is nil. Means we just store the source downloaded data to disk cache.
 默认值是nil,通过设置该值,可以将下载好的数据压缩成指定格式,保存到disk中
* @code
 SDWebImageManager.sharedManager.cacheSerializer = [SDWebImageCacheSerializer cacheSerializerWithBlock:^NSData * _Nullable(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL) {
    SDImageFormat format = [NSData sd_imageFormatForImageData:data];
    switch (format) {
        case SDImageFormatWebP:
            return image.images ? data : nil;
        default:
            return data;
    }
}];
 */
@property (nonatomic, strong, nullable) id<SDWebImageCacheSerializer> cacheSerializer;

/**
 //对所有的图片请求options设置,都可以统一放到当前的属性中进行
@code
 SDWebImageManager.sharedManager.optionsProcessor = [SDWebImageOptionsProcessor optionsProcessorWithBlock:^SDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
     // Only do animation on `SDAnimatedImageView`
     if (!context[SDWebImageContextAnimatedImageClass]) {
        options |= SDWebImageDecodeFirstFrameOnly;
     }
     // Do not force decode for png url
     if ([url.lastPathComponent isEqualToString:@"png"]) {
        options |= SDWebImageAvoidDecodeImage;
     }
     // Always use screen scale factor
     SDWebImageMutableContext *mutableContext = [NSDictionary dictionaryWithDictionary:context];
     mutableContext[SDWebImageContextImageScaleFactor] = @(UIScreen.mainScreen.scale);
     context = [mutableContext copy];
 
     return [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
 }];
 */
@property (nonatomic, strong, nullable) id<SDWebImageOptionsProcessor> optionsProcessor;

/**
 判断当前是否有正在执行的任务
 */
@property (nonatomic, assign, readonly, getter=isRunning) BOOL running;

/**
 默认是nil,设置完成之后可以通过SDImageCache.sharedImageCache获取
 */
@property (nonatomic, class, nullable) id<SDImageCache> defaultImageCache;

/**
 //默认是nil,设置完成之后可以通过SDWebImageDownloader.sharedDownloader获取
 */
@property (nonatomic, class, nullable) id<SDImageLoader> defaultImageLoader;

/**
 //放回当前SDWebImageManager单例对象
 */
@property (nonatomic, class, readonly, nonnull) SDWebImageManager *sharedManager;

/**
 //传入cache/loader初始化当前对象
 */
- (nonnull instancetype)initWithCache:(nonnull id<SDImageCache>)cache loader:(nonnull id<SDImageLoader>)loader NS_DESIGNATED_INITIALIZER;

/**
 *对外界暴露的加载图片接口1
 *
 * @param url           URL
 * @param options        图片的加载样式
 * @param progressBlock  进度block,默认在后台队列中
 * @param completedBlock  完成回调的block

 * @return Returns 返回SDWebImageCombinedOperation对象,用户直接操作当前的加载任务
 */
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageOptions)options
                                                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                                 completed:(nonnull SDInternalCompletionBlock)completedBlock;

/**
 *暴露给外界的加载图片接口2
 * @param url            URL
 * @param options        图片的加载样式
 * @param context        context内容枚举 `SDWebImageContextOption`设置图片样式/缓存格式/
cache的key
 * @param progressBlock  进度block,默认在后台队列中
 * @param completedBlock  完成回调的block
 *
 * @return Returns an instance of SDWebImageCombinedOperation, which you can cancel the loading process.
 */
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                                 completed:(nonnull SDInternalCompletionBlock)completedBlock;

/**
 //取消所有的当前operations
 */
- (void)cancelAll;

/**
 //通过一个URL,返回一个cache中的key
 */
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

在.m中又添加了几个私有属性

//修改为readwrite
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader;
//NSMutableSet 保存当前的请求中已经失败的URL
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
//创建网络失败的信号量,防止数据冲突
@property (strong, nonatomic, nonnull) dispatch_semaphore_t failedURLsLock; 
//当前所有的加载图片任务
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
//创建加载中的信号量,防止数据冲突
@property (strong, nonatomic, nonnull) dispatch_semaphore_t runningOperationsLock;

我们来看一下当前类中的核心方法

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    // 外部警告completedBlock为nil
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // 对url的处理
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

   //如果为NSNull,那么可能会造成crash
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    //为了在当前的单例类中区分每次的加载图片任务,需要设置一个operation,表示一个加载任务
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;

    //从请求失败的set中查找当前的URL,并加锁
    BOOL isFailedUrl = NO;
    if (url) {
        //SD_LOCK 其实是一个信号量宏 - dispatch_semaphore_wait
        SD_LOCK(self.failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(self.failedURLsLock);
    }

    //假如从失败set中查找到了,并且没有设置失败之后的再次请求
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        //包装返回值,进行返回
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
        return operation;
    }
    
    //对当前正在加载中的operations的set加锁,并将当前的operation放入runningOperations
    SD_LOCK(self.runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(self.runningOperationsLock);
    
    //将options和context放到统一的对象SDWebImageOptionsResult中进行管理
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
    
    //开始去从cache/load加载图片
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

开始查询UIImage或者下载UIImage

- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Check whether we should query cache
    //首先判断是否需要去查询缓存
    BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
    if (shouldQueryCache) {
        //cache对应的url是否自定义
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
        //传入url,通过外部重新设置的格式,来重新生成cache key
        NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
        @weakify(operation);
        
        //operation的当前缓存当前缓存查找顺序
        //在SDImageCache中,查找对应的缓存图片信息
        operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            if (!operation || operation.isCancelled) {
                //在runningOperations中移除operation
                [self safelyRemoveOperationFromRunning:operation];
                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];
    }
}

相关文章

网友评论

    本文标题:深入理解SDWebImage(二)

    本文链接:https://www.haomeiwen.com/subject/unkhjctx.html