按照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];
}
}
网友评论