深入理解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