概述
关于SDWebImage下载器的大概流程如下,总体上SDWebImageDownloader负责下载任务的调度,SDWebImageDownloaderOperation负责具体的下载任务。
一、SDWebImageDownloaderOperation
SDWebImageDownloaderOperation其实就是一个下载任务的抽象,继承自NSOperation,我们就复习一下,NSOperation的知识。
1.1、NSOperation的生命周期
说到NSOperation不得不说两个方法,-main()
和-start()
两个方法,他们有什么区别呢,区别如下:
-main()
:当main执行完毕后,finish就会主动置为空,当前op出队列,下一个OP开始执行。-start()
:start方法执行完毕,并不会影响当前op的生命周期,我们可以通过重写属性,来控制成员变量finish的值,当符合结束条件时finish置为yes,就会主动执行下一个任务。
1.2、再复习一个知识点,因为原本NSOperation中finish是只读的,如何重写一个finish属性使其实内部可读写,外部还是只读的呢
@interface TestOperationOne()
//写在类扩展里
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@end
@implementation TestOperationOne
//写在实现中
@synthesize finished = _finished;
@end
//重写setter方法是为了支持KVO
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
如上是实现原理,先复习一下这两个关键字的作用
- @property默认生成setter个getter方法,而且会检测是否有(_属性名)对应的成员变量,如果没有会自动生成。
-
@synthesize可以使属性关联到对应的属性上,这样setter和getter方法操作的就是关联的成员变量了。
如果我们只写了重写了finished
@interface TestOperationOne()
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@end
会报警告
image.png
为什么呢,因为我们重写了@property会默认将我们写的finish与_finish这个成员变量绑定,但是_finish已经被他的父类NSOperation关联了,编译器不知道怎么办了,该类中的finished没有关联的成员变量了。需要我们制定以下@synthesize finished = _finished
OK啦。
1.3、初始化方法
//传入Request和session还有下载选项,并保存
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
//存储信息
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_callbacksLock = dispatch_semaphore_create(1);
//创建同步队列
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
#if SD_UIKIT
_backgroundTaskId = UIBackgroundTaskInvalid;
#endif
}
return self;
}
1.4、回调block的处理
添加需要回调的block,通过copy可以将block拷贝到堆内存中,用引用计数管理内存,最终block会存储在一个字典中@[kProgressCallbackKey:progressBlock,kCompletedCallbackKey:completedBlock]
以这样的形式,并且添加到self.callbacksLock
数组中,为什么这样这样做呢??
这是因为DSWebImageDownloader
中会根据传入的URL判断这个URL是否正在下载,如果正在下载则不会重复下载任务,会把block传入对应的OP中并存储到self.callbacksLock中,回调的时候会将数组self.callbacksLock
中的block全部进行回调。
说白了就是做了一个下载的去重逻辑。并对重复调用下载的地方也同样进行回调
//添加需要回调的block
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
//创建可变字典callbacks存储[kProgressCallbackKey:progressBlock,
// kCompletedCallbackKey:completedBlock]
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks];
UNLOCK(self.callbacksLock);
return callbacks;
}
1.5、SDWebImageDownloaderOperation的相关操作
-
- (void)start
任务开始,在OP对象被添加到队列中时会自动调用该方法。是下载操作的开端。
//开始
- (void)start {
//加锁保护
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
//判断是否开启后台下载
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
//beginBackgroundTaskWithExpirationHandler会像系统申请后台额外时间,如果额外时间还没有干完,就进入handle回调,处理进入后台。
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
[wself cancel];
}];
}
#endif
NSURLSession *session = self.unownedSession;
if (!session) {
//如果会话为nil,则重新创建
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* 为这个任务创建会话
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
//注意这里面创建的保存在ownedSession里面了,因为这个是OP自己创建的session,不用的时候要置空,后面会讲到
self.ownedSession = session;
}
//如果选项包含忽略缓存的response
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// 获取缓存的数据供以后检查
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
// cachedResponseForRequest不安全获取cacheResponse
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
//存储缓存的response数据
self.cachedData = cachedResponse.data;
}
}
//创建任务,在URL会话中执行的任务,如下载特定资源。
self.dataTask = [session dataTaskWithRequest:self.request];
NSLog(@"session:%@ dataTask:%@",self.unownedSession,self.dataTask);
//维护执行状态
self.executing = YES;
}
if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
//通过优先级处理
if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
//高优先级
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
//低优先级
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
}
#pragma clang diagnostic pop
[self.dataTask resume];
//遍历所有的progressBlock发送消息0
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//发送下载开始的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
}
}
-
- (void)cancel
任务取消,这里将finish置为YES,当前OP出队列
//op取消
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
//取消内部的操作
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.dataTask) {
//取消下载任务
[self.dataTask cancel];
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//发送下载任务通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
});
//设置
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
-
- (void)done
任务完成,finish也置为YES,OP出队列
//结束
- (void)done {
//设置finish
self.finished = YES;
//设置excuting
self.executing = NO;
[self reset];
}
-
- (void)reset
任务重置,
//重置数据
- (void)reset {
LOCK(self.callbacksLock);
//删除callbackBlocks中保存的block
[self.callbackBlocks removeAllObjects];
UNLOCK(self.callbacksLock);
@synchronized (self) {
//清理sessionTask
self.dataTask = nil;
//清楚session
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
#if SD_UIKIT
//这里判断系统返回的后台任务标志是否有效,如果有效调用endBackgroundTask方法,和beginBackgroundTaskWithExpirationHandler承兑出现
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
// If backgroundTaskId != UIBackgroundTaskInvalid, sharedApplication is always exist
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
}
1.6、URSSessionDelegate的处理
session任务获得初始的回复,处理逻辑,这里面处理了304的情况
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
//数据的大小
NSInteger expected = (NSInteger)response.expectedContentLength;
//容错处理
expected = expected > 0 ? expected : 0;
//数据大小
self.expectedSize = expected;
//存储response
self.response = response;
//获取状态码
NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
//如果小于400 valid设置为yes
BOOL valid = statusCode < 400;
//在客户端有缓存的时候,第二次请求会在header中传上一期请求的时间If-Modified-Since 和 If-None-Match,服务器判断数据是否有修改,如果没有直接返回304 not Modified 并且没有响应体,所以这里判断服务器返回304,且没有缓存数据的时候是异常情况。
if (statusCode == 304 && !self.cachedData) {
valid = NO;
}
if (valid) {
//获取存储的block设置回调
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
} else {
// 如果状态错误那么设置为取消
// Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle
disposition = NSURLSessionResponseCancel;
}
__block typeof(self) strongSelf = self;
//发送通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:strongSelf];
});
if (completionHandler) {
completionHandler(disposition);
}
}
会话获取数据时回调,这里如果选择了渐进解码,会不断的回调completeBlock。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//创建一个预期大小的data
if (!self.imageData) {
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
}
//data中添加数据
[self.imageData appendData:data];
//选项判断,是否渐进式下载
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// 获取图片数据
__block NSData *imageData = [self.imageData copy];
// 获取数据的所有大小
const NSInteger totalSize = imageData.length;
// f判断是否下载完成
BOOL finished = (totalSize >= self.expectedSize);
if (!self.progressiveCoder) {
//这里需要初始化一个新的渐进解码器
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) {
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] init];
break;
}
}
}
// 在全局的同步队列中进行解码
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
if (image) {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
});
}
//做回调
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
}
}
下载完成的回调
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
//数据操作互斥锁
@synchronized(self) {
self.dataTask = nil;
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:strongSelf];
}
});
}
// make sure to call `[self done]` to mark operation as finished
if (error) {
[self callCompletionBlocksWithError:error];
[self done];
} else {
//如果存在block,那么解码
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* If you specified to use `NSURLCache`, then the response you get here is what you need.
*/
__block NSData *imageData = [self.imageData copy];
self.imageData = nil;
if (imageData) {
/** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
* then we should check if the cached data is equal to image data
*/
//这里判断是缓存数据
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
[self done];
} else {
// 解码数据
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
//数据解码
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
//返回缓存的Key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//调整图片倍率
image = [self scaledImageForKey:key image:image];
// 对于gif不强制解码
// because there has imageCoder which can change `image` or `imageData` to static image, lose the animated feature totally.
BOOL shouldDecode = !image.images && image.sd_imageFormat != SDImageFormatGIF;
if (shouldDecode) {
if (self.shouldDecompressImages) {
//图片的解压缩
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
}
}
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
//返回完成回调
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
[self done];
}
});
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
[self done];
}
} else {
[self done];
}
}
}
二、SDWebImageDownloader
接下来看看细节从SDWebImageDownloader开始
先看看下载器支持的操作,很多哦,都在这个枚举里面呢,按位枚举可以或或或
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
/**
* 设置低优先级
*/
SDWebImageDownloaderLowPriority = 1 << 0,
/**
* 采用渐进式下载,就是一点一点显示的辣种。
*/
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* 使用URL的缓存
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* 忽略缓存的响应
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* 支持后台下载
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* 设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* 允许不受信任的SSL证书
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* 设置高优先级
*/
SDWebImageDownloaderHighPriority = 1 << 7,
/**
* 按比例缩小图片
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
还有一个枚举,是任务的处理顺序。
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* 默认是队列形式的先进先出
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
* 栈类型的后进先出
*/
SDWebImageDownloaderLIFOExecutionOrder
};
1.1、初始化
初始化方法负责初始化各种条件变量
- session 负责协调一组相关网络传输任务的对象,在下载器中所有的下载任务默认都是这个session负责生成下载任务。
- _downloadQueue 一个NSOperationQueue负责OP的分发调配。
- _URLOperations 存储当前正在执行的OP,为了给当前正在执行的OP下发网络回调。
- _HTTPHeaders 设置,默认设置了UA和Accept
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
//设置op的class
_operationClass = [SDWebImageDownloaderOperation class];
//是否解压图片
_shouldDecompressImages = YES;
//操作顺序
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
//下载队列
_downloadQueue = [NSOperationQueue new];
//最大并发
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
//url操作
_URLOperations = [NSMutableDictionary new];
//headerDix
SDHTTPHeadersMutableDictionary *headerDictionary = [SDHTTPHeadersMutableDictionary dictionary];
//设置UA
NSString *userAgent = nil;
#if SD_UIKIT
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif SD_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif SD_MAC
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
headerDictionary[@"User-Agent"] = userAgent;
}
#ifdef SD_WEBP
headerDictionary[@"Accept"] = @"image/webp,image/*;q=0.8";
#else
headerDictionary[@"Accept"] = @"image/*;q=0.8";
#endif
_HTTPHeaders = headerDictionary;
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
_downloadTimeout = 15.0;
[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}
- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
[self cancelAllDownloads];
if (self.session) {
[self.session invalidateAndCancel];
}
//设置超时时间
sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
/**
* 创建session
* 我们将nil作为委托队列发送,以便会话创建一个串行操作队列来执行所有委托
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
1.2、创建SDWebImageDownloaderOperation
主要是处理了一些我们传入的网络相关的选项
- SDWebImageDownloaderUseNSURLCache 是否用request缓存
- SDWebImageDownloaderHandleCookies 是否处理cookie
- SDWebImageDownloaderAllowInvalidSSLCertificates 是否允许SSL证书不受信任
//创建一个根据URL创建一个下载OP
- (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options {
NSTimeInterval timeoutInterval = self.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise‘
//缓存规则是否使用URL缓存
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//设置urlRequest
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
//处理cookie
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (self.headersFilter) {
request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = [self allHTTPHeaderFields];
}
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
operation.shouldDecompressImages = self.shouldDecompressImages;
if (self.urlCredential) {
operation.credential = self.urlCredential;
} else if (self.username && self.password) {
operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
}
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[self.lastAddedOperation addDependency:operation];
self.lastAddedOperation = operation;
}
return operation;
}
1.3、下载任务
//添加下载任务并且返回一个downloadToken
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 由于没有完成回调不会返回图片数据,所以没有complete是没有意义的
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
LOCK(self.operationsLock);
//获取出OP的类型URLOperations是正在执行的OP
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
//如果没有url对应的OP,或者任务完成,任务取消
if (!operation || operation.isFinished || operation.isCancelled) {
//创建一个OP
operation = [self createDownloaderOperationWithUrl:url options:options];
__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);
};
//添加进URLOperations中
[self.URLOperations setObject:operation forKey:url];
// 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.
//添加进OP
[self.downloadQueue addOperation:operation];
}
else if (!operation.isExecuting) {
//如果OP没有正在执行,说明正在等待下载,设置重新设置一下优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
UNLOCK(self.operationsLock);
//创建一个广告删除的token
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
网友评论