SDWebImageManage中的SDImageLoader
类管理任务下载
SDWebImageDownloader
和SDWebImageDownloaderOperation
处理图片的网络加载,在SDWebImageManage
通过属性imageLoader
持有SDWebImageDownloader
,调用内部方法从网络加载图片。
使用SDWebImageDownloaderOperation
对象下载图片
interface SDWebImageManager : NSObject
@property (strong, nonatomic, readonly, nonnull) id<SDImageLoader> imageLoader;
@end
SDWebImageDownloader
本文主要从以下几个方面了解
- 定义
SDWebImageDownloaderOptions
枚举类型,设置图片从网络加载的不同情况 - 定义并管理
NSURLSession
对象,通过这个对象来做网络请求,并且实现对象的代理方法 - 定义
NSURLRequest
对象,管理请求头的封装 - 对于每一个网络请求,通过一个
SDWebImageDownloaderOperation
自定义NSOperation
来操作网络下载 - 管理网络加载过程和完成的回调工作,通过
addProgressCallback
实现
枚举类型
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/*
*默认情况下,http请求阻止使用NSURLCache对象。如果设置了这个标记,则NSURLCache会被http请求使用。
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/*
*如果image/imageData是从NSURLCache返回的。则completion这个回调会返回nil。
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/*
*如果app进入后台模式,是否继续下载。这个是通过在后台申请时间来完成这个操作。如果指定的时间范围内没有完成,则直接取消下载。
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/*
处理缓存在`NSHTTPCookieStore`对象里面的cookie。通过设置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`来实现的。
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/*
*允许非信任的SSL证书请求。
*在测试的时候很有用。但是正式环境要小心使用。
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/*
* 默认情况下,图片加载的顺序是根据加入队列的顺序加载的。但是这个标记会把任务加入队列的最前面。
*/
SDWebImageDownloaderHighPriority = 1 << 7,
/*
*默认情况下,图片会按照他的原始大小来解码显示。这个属性会调整图片的尺寸到合适的大小根据设备的内存限制。
*如果`SDWebImageProgressiveDownload`标记被设置了,则这个flag不起作用。
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
- SDWebImageDownloader
@interface SDWebImageDownloader : NSObject <SDImageLoader>
@property (nonatomic, copy, readonly, nonnull) SDWebImageDownloaderConfig *config;
/**
* Gets/Sets the download queue suspension state.
*/
@property (nonatomic, assign, getter=isSuspended) BOOL suspended;
/**
* Shows the current amount of downloads that still need to be downloaded
*/
@property (nonatomic, assign, readonly) NSUInteger currentDownloadCount;
@end
- SDWebImageDownloaderConfig
@interface SDWebImageDownloaderConfig : NSObject <NSCopying>
/**
* The maximum number of concurrent downloads.
* Defaults to 6.
*/
@property (nonatomic, assign) NSInteger maxConcurrentDownloads;
/**
* The timeout value (in seconds) for each download operation.
* Defaults to 15.0.
*/
@property (nonatomic, assign) NSTimeInterval downloadTimeout;
@end
- (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 {
id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
if (!imageLoader) {
imageLoader = self.imageLoader;
}
operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
}];
}
- requestImageWithURL
SDWebImageDownloader.m
- (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];
}
SDWebImageDownloader.m
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
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(self.operationsLock);
id downloadOperationCancelToken;
// 默认 URLOperations 内是没有 operation 的,operation 为空
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
if (!operation || operation.isFinished || operation.isCancelled) {
// 创建一个 operation,详见 1-5-7-4-5-5-1
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
// 创建 operation 出错,调用 completedBlock 回调错误信息
if (!operation) {
SD_UNLOCK(self.operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
// self 强引用 URLOperations,URLOperations 强引用 operation,因此 operation.completionBlock 内要使用弱引用
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
// 当 operation 完成动作后,要将其从 URLOperations 中移除
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
};
// 创建完 operation 后如果不出意外则保存在 URLOperations(可变字典) 中
self.URLOperations[url] = operation;
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
// 一定是在设置好所需的配置之后,再把 operation 添加到操作队列中
// addOperation: 操作不会同步的执行 operation.completionBlock,因此不会引起循环引用
// 主线:添加到队列中operation就会开始执行,开始下载;详见 1-5-7-4-5-5-3
[self.downloadQueue addOperation:operation];
// 将 progressBlock 和 completedBlock 保存起来,等待下载(NSURLSession)的代理方法来调用,详见 1-5-7-4-5-5-2
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
} else {
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
// 当重用 download operation 来附加更多 callbacks 的时候,由于 callbacks 的 getter 可能在其它的队列的原因可能会有线程安全问题
// 因此在这里给 operation 加锁,并在 SDWebImageDownloaderOperation 类中使用 @synchonzied (self),以确保这两个类的线程安全
@synchronized (operation) {
// 仍然参考 1-5-7-4-5-5-2
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
// 如果 operation 未在执行,可以设置一下优先级
if (!operation.isExecuting) {
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
}
SD_UNLOCK(self.operationsLock);
// 创建一个 SDWebImageDownloadToken 实例,传入 operation;详见 1-5-7-4-5-5-4
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
// token.downloadOperationCancelToken 是弱引用,SDWebImageDownloaderOperation 的 callbackBlocks 属性才是持有它的人
// 可以参考 1-5-7-4-5-5-2
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
网友评论