SDWebImageDownloaderOperation是针对图片的某个具体的下载任务。SDWebImageDownloader角色则是类似于一个下载器,可以发起执行一个或多个下载任务,并对这些任务进行管理:任务的执行顺序,停止任务,取消任务,控制任务的并发量等。
首先来看一下SDWebImageDownloader.h头文件
定义了一个下载的配置选项SDWebImageDownloaderOptions
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
//下载优先级低
SDWebImageDownloaderLowPriority = 1,
SDWebImageDownloaderLowPriority = 2,
//渐进式的解码下载图片
SDWebImageDownloaderProgressiveDownload = 1 << 1,
//配置了这个选项就会使用系统默认的缓存机制,否则使用SDImageCache缓存
SDWebImageDownloaderUseNSURLCache = 1 << 2,
//结合SDWebImageDownloaderUseNSURLCache选项,忽略缓存的图片
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
//当app退到后台,申请后台继续下载
SDWebImageDownloaderContinueInBackground = 1 << 4,
//设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;
SDWebImageDownloaderHandleCookies = 1 << 5,
//允许非法证书请求
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
//下载优先级高
SDWebImageDownloaderHighPriority = 1 << 7,
//如果下载的图片太大(大于60M)则对图片进行缩小
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
�下面是任务执行的顺序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
//先入先出
SDWebImageDownloaderFIFOExecutionOrder,
//先入后出
SDWebImageDownloaderLIFOExecutionOrder
};
这里有个类SDWebImageDownloadToken 用于关联某个特定的SDWebImageDownloaderOperation,可以让外界方便的取消下载中的任务。
//这个类遵守SDWebImageOperation协议,实现了cancel方法
@interface SDWebImageDownloadToken : NSObject <SDWebImageOperation>
//图片url
@property (nonatomic, strong, nullable) NSURL *url;
//SDWebImageDownloaderOperation: - (nullable id)addHandlersForProgress:completed:后返回的字典结构,里面包含这个operation的progressBlock和completedBlock
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
//关联的SDWebImageDownloaderOperation
@property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperationInterface> *downloadOperation;
@end
@implementation SDWebImageDownloadToken
- (void)cancel {
if (self.downloadOperation) {
SDWebImageDownloadToken *cancelToken = self.downloadOperationCancelToken;
if (cancelToken) {
//调用SDWebImageDownloaderOperation的 cancel:方法来移除回调函数,当所有回调函数都移除了,这个下载任务就会被取消掉
[self.downloadOperation cancel:cancelToken];
}
}
}
@end
下面来看看SDWebImageDownloader向外部暴露的属性和方法:
@interface SDWebImageDownloader : NSObject
//是否要对下载的图片预绘制解码
@property (assign, nonatomic) BOOL shouldDecompressImages;
//最大并发下载数
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//当前下载任务数
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//超市时间,默认15s
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//内部用于配置NSUrlSession的NSURLSessionConfiguration
@property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;
//任务的执行顺序
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
//单例获取
+ (nonnull instancetype)sharedDownloader;
//https证书
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
@property (strong, nonatomic, nullable) NSString *username;
@property (strong, nonatomic, nullable) NSString *password;
//用于过滤http请求头参数
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
//
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
//设置http请求头参数
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
//获取指定http请求头信息
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
//指定自定义的下载类
- (void)setOperationClass:(nullable Class)operationClass;
//下载图片,设置下载选项,指定回调block
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//取消
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
//暂停
- (void)setSuspended:(BOOL)suspended;
//取消所有下载任务
- (void)cancelAllDownloads;
//重新指定NSURLSessionConfiguration配置NSUrlSession
- (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
//使NSUrlSession失效,cancelPendingOperations:是否取消下载中的任务
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;
@end
接下来看看SDWebImageDownloader 的实现
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
//最后添加的任务:当指定为LIFO,如果有新下载任务,则之前最后添加的任务会依赖于新任务执行完达到LIFO效果
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
//自定义的下载类
@property (assign, nonatomic, nullable) Class operationClass;
//管理的下载任务集合
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
//request请求头
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
//保证任务集合在多线程数据安全所用的信号量
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock;
//保证HTTPHeaders在多线程数据安全所用的信号量
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock;
@property (strong, nonatomic) NSURLSession *session;
@end
在initialize中为请求添加指示器:
+ (void)initialize {
// 我们需要额外导入SDNetworkActivityIndicator.h
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//通过runtime来实例化一个指示器
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
// 因为指示器是用sharedActivityIndicator获取的单例,添加监听前先移除
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"startActivity")
name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"stopActivity")
name:SDWebImageDownloadStopNotification object:nil];
}
}
下面来看构造函数:
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
//默认预绘制解码下载图片
_shouldDecompressImages = YES;
//默认FIFO执行顺序
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
//默认并发数为6
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
//默认超时时间15s
_downloadTimeout = 15.0;
[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}
下面来看看如何发起和管理一个下载任务
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock
内部调用:
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback
来为一个SDWebImageDownloaderOperation 添加progressBlock和completedBlock。同时传递了一个createCallback来实例化一个SDWebImageDownloaderOperation。
我们来看看如何实例化一个SDWebImageDownloaderOperation:
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// 是否使用系统默认的缓存策略
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
//如果指定过滤请求头,使用block过滤
request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
}
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//通过下载选项配置请求优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
//指定LIFO
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
下面来看看如何关联回调block:
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
LOCK(self.operationsLock);
//首先确认下这个operation是否已经在任务集合中,保证当前针对一个url只有一个下载任务,防止同一时刻反复下载一张图片
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished) {
operation = createCallback();
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
//添加到下载队列,开始下载
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);
//为operation增加回调block
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
//配置SDWebImageDownloadToken,包装了回调函数,operation,url。返回给外包调用者来管理下载任务
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
关于NSURLSession代理方法:
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
}
都是通过这种形式来把代理操作转发到SDWebImageDownloaderOperation的代理方法里面去执行。
operationWithTask通过NSURLSessionTask的taskIdentifier匹配来找出相对应的SDWebImageDownloaderOperation。
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
SDWebImageDownloaderOperation *returnOperation = nil;
for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
return returnOperation;
}
网友评论