4>SDWebImageDownloader
SDWebImageDownloader是以单例存在,对图片下载管理,进行一些全局的配置。如下:
1).设置最大并发数,下载时间默认15秒,是否压缩图片和下载顺序等。
2).设置operation的请求头信息,负责生成单个SDWebImageDownloaderOperation以及取消operation等。
3).设置下载的策略SDWebImageDownloaderOptions。
SDWebImageDownloaderOptions
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
//图片下载设置低优先级
SDWebImageDownloaderLowPriority = 1 << 0,
//分进度下载图片
SDWebImageDownloaderProgressiveDownload = 1 << 1,
//设置了该选项则使用NSURLCache缓存策略
SDWebImageDownloaderUseNSURLCache = 1 << 2,
//如果图片从NSURLCache读取,则回到block中的image和imageData置为nil
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
//在iOS4之后,如果设置了此选项则在后台依然下载图片,申请额外的时间来进行后台下载
SDWebImageDownloaderContinueInBackground = 1 << 4,
// 处理存储在NSHTTPCookieStore中的cookies
SDWebImageDownloaderHandleCookies = 1 << 5,
//允许非信任的SSL证书
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
//设置图片下载高优先级
SDWebImageDownloaderHighPriority = 1 << 7,
//对大图片进行缩放
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
SDWebImageDownloaderExecutionOrder
SDWebImageDownloaderExecutionOrder是下载执行的顺序:
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
//按照队列先进先出的顺序下载
SDWebImageDownloaderFIFOExecutionOrder,
//按照栈后进先出进行下载
SDWebImageDownloaderLIFOExecutionOrder
};
SDWebImageDownloadToken
是作为下载操作的唯一标识,在创建operation的时候初始化绑定,当需要去cancel操作的时候就需要这个token。
@interface SDWebImageDownloadToken : NSObject
@property (nonatomic, strong, nullable) NSURL *url;
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
@end
SDWebImageDownloader.h属性方法:
@interface SDWebImageDownloader : NSObject
//是否压缩图片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//设置最大并发数量
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//获取当前下载的数量
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//下载时间 默认15秒
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//NSURLSession配置一些请求所需要的策略
@property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;
//设置下载顺序
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
//生成单例 并返回当前实例
+ (nonnull instancetype)sharedDownloader;
//设置身份认证
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
//设置用户名
@property (strong, nonatomic, nullable) NSString *username;
//设置密码
@property (strong, nonatomic, nullable) NSString *password;
//针对header进行过滤的block
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
//生成一个实例,利用特定的配置sessionConfiguration
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
//针对request添加HTTP header
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
//返回某个header field的value
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
//设置一个SDWebImageDownloaderOperation的子类赋值给_operationClass
- (void)setOperationClass:(nullable Class)operationClass;
//根据指定的url异步加载图片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//取消指定token的下载
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
//设置下载队列是否挂起
- (void)setSuspended:(BOOL)suspended;
//取消所有的下载
- (void)cancelAllDownloads;
//强制给self设置一个新的NSURLSession
- (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
//取消operation并且session设置为Invalidates
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;
@end
SDWebImageDownloader.m方法:
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//定义下载队列
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
//定义下载的上个operation 作用是为了后面的下载依赖
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
// 图片下载操作类
@property (assign, nonatomic, nullable) Class operationClass;
//下载url作为key value是具体的下载operation 用字典来存储,方便cancel等操作
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
//HTTP请求头
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
//定义队列用来保证处理所有下载操作线程安全,将任务利用dispatch_barrier_sync队列barrierQueue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
//利用NSURLSession进行网络请求
@property (strong, nonatomic) NSURLSession *session;
@end
SDWebImageDownloader.m的关键方法:
//这里的initialize就是开篇讲到的知识点,不过这里还有一个值得学习的点就是NSClassFromString,用来判断当前程序是否有指定字符串的类,如果没有回返回一个空对象,id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];这样配合对一个不确定进行初始化
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
// To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
// Remove observer in case it was previously added.
[[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)init;和- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration;方法中进行初始化操作,不展开说明。
downloadImageWithURL方法如下:
//给定指定URL下载图片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
//在下面的block中创建operation
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
//设置下载时间
NSTimeInterval timeoutInterval = sself.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
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//创建request 设置请求缓存策略 下载时间
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
//HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求. 这个会很大的提高网络请求的效率,但是也可能会出问题
//因为客户端无法正确的匹配请求与响应, 所以这依赖于服务器必须保证,响应的顺序与客户端请求的顺序一致.如果服务器不能保证这一点, 那可能导致响应和请求混乱.
request.HTTPShouldUsePipelining = YES;
//设置请求头
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
//重头戏 在这里创建operation对象
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;
}
//添加operation到下载队列
[sself.downloadQueue addOperation:operation];
//设置下载的顺序 是按照队列还是栈
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
//包装callbackBlocks,URLOperations和token
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// 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 != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
__block SDWebImageDownloadToken *token = nil;
//生成URLOperations字典 下载url作为key value是具体的下载operation 方便下载cancel等操作
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
});
};
}
//将进度progressBlock和下载结束completedBlock封装成字典SDCallbacksDictionary,装入数组callbackBlocks,
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
//这里生成token标识,这个token说白了就是为了在SDWebImageManager中调用[self.imageDownloader cancel:subOperationToken];来做取消的
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
SDWebImageDownloader中的其他方法都是比较简单的例如下面,不单独拿出来讲,大家看一眼就懂了。
- (void)setSuspended:(BOOL)suspended;
- (void)cancelAllDownloads ;
- (NSInteger)maxConcurrentDownloads
>5 SDWebImageDownloaderOperation
SDWebImageDownloaderOperation继承自NSOperation,是具体的执行图片下载的单位。负责生成NSURLSessionTask进行图片请求,支持下载取消和后台下载,在下载中及时汇报下载进度,在下载成功后,对图片进行解码,缩放和压缩等操作。
SDWebImageDownloaderOperation.h文件的具体注解:
//开始下载通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
//收到response通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
//停止下载通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
//结束下载通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
//如果你想要使用一个自定义的downloader对象,那么这个对象需要继承自NSOperation,并且实现这个协议
@protocol SDWebImageDownloaderOperationInterface<NSObject>
//SDWebImageDownloaderOperationInterface的初始化方法
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
//绑定DownloaderOperation的下载进度block和结束block
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//返回当前下载操作的图片是否应该压缩
- (BOOL)shouldDecompressImages;
//设置是否应该压缩图片
- (void)setShouldDecompressImages:(BOOL)value;
//返回身份认证
- (nullable NSURLCredential *)credential;
//设置身份认证
- (void)setCredential:(nullable NSURLCredential *)value;
@end
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//operation的请求request
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;
//operation的task
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
//图片是否可以被压缩属性
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* Was used to determine whether the URL connection should consult the credential storage for authenticating the connection.
* @deprecated Not used for a couple of versions
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
//身份认证
@property (nonatomic, strong, nullable) NSURLCredential *credential;
//下载配置策略
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
//期望data的大小size
@property (assign, nonatomic) NSInteger expectedSize;
//operation的response
@property (strong, nonatomic, nullable) NSURLResponse *response;
//初始化一个SDWebImageDownloaderOperation对象
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
//绑定DownloaderOperation的下载进度block和结束block
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//SDWebImageOperation的协议方法
- (BOOL)cancel:(nullable id)token;
@end
SDWebImageDownloaderOperation.m的具体注解文件:
//开始下载通知赋值
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
//收到response通知赋值
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
//停止下载通知赋值
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
//结束下载通知赋值
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
//静态全局变量作为下载进度block字典存储的key
static NSString *const kProgressCallbackKey = @"progress";
//静态全局变量作为结束下载block字典存储的key
static NSString *const kCompletedCallbackKey = @"completed";
//声明类型SDCallbacksDictionary key=NSString,value=id
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@interface SDWebImageDownloaderOperation ()
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
//是否正在执行
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
//是否下载结束
@property (assign, nonatomic, getter = isFinished) BOOL finished;
//图片的imageData
@property (strong, nonatomic, nullable) NSMutableData *imageData;
//缓存的data
@property (copy, nonatomic, nullable) NSData *cachedData;
//网络请求session,注意这里使用的属性修饰符是weak,因为unownedSession是从SDWebImageDownloader传过来的,所以需要SDWebImageDownloader管理
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
//网络请求session,注意这里使用的属性修饰符是strong,如果unownedSession是nil,我们需要手动创建一个并且管理它的生命周期和代理方法
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;
//网络请求具体的task
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
//全局并行队列,控制数据
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
#if SD_UIKIT
//如果用户设置了后台继续加载选项,通过backgroundTask来继续下载图片
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
//图片解码器,有意思的是如果图片没有完全下载完成时也可以解码展示部分图片
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
@end
@implementation SDWebImageDownloaderOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
//init方法
- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:0];
}
//初始化方法,进行一些基础数据的赋值配置
- (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;
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
//绑定DownloaderOperation的下载进度block和结束block
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
//使用dispatch_barrier_async方法异步方式不阻塞当前线程,串行执行添加进数组的操作
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
//根据key获取回调块数组中所有对应key的回调块
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
//同步方式执行,阻塞当前线程也阻塞队列
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
//我们需要删除数组中的[NSNull null]元素,因为可能callback没有回调块
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return [callbacks copy]; // strip mutability here
}
//取消方法
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
//同步方法阻塞当前队列和当前线程
dispatch_barrier_sync(self.barrierQueue, ^{
//删除数组中回调块数组中的token对象,token是key为string,value是block的字典
[self.callbackBlocks removeObjectIdenticalTo:token];
//判断数组是否为0.则取消下载任务
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
//重写operation的start方法,当任务添加到NSOperationQueue后会执行该方法,启动下载任务
- (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)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
//获取网络请求的缓存数据
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
NSURLSession *session = self.unownedSession;
//判断unownedSession是否为了nil,如果是nil则重新创建一个ownedSession
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
//超时时间15s
sessionConfig.timeoutIntervalForRequest = 15;
//delegateQueue为nil,所以回调方法默认在一个子线程的串行队列中执行
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
//使用session来创建一个NSURLSessionDataTask类型下载任务
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
//开始执行任务
[self.dataTask resume];
if (self.dataTask) {
//任务开启,遍历进度块数组 执行第一个下载进度 进度为0
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//在主线程发送通知,并将self传出去,为什么在主线程发送呢?
//因为在哪个线程发送通知就在哪个线程接受通知,这样如果在接受通知方法中执行修改UI的操作也不会有问题
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
//如果创建tataTask失败就执行失败的回调块
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#if SD_UIKIT
//后台继续下载
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
//SDWebImageOperation的协议方法
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
//取消下载
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
//如果下载图片的任务仍在 则立即取消cancel,并且发送结束下载的通知
if (self.dataTask) {
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
//下载完成后调用的方法
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
}
//重新设置数据的方法
- (void)reset {
__weak typeof(self) weakSelf = self;
//删除回调块字典数组的所有元素
dispatch_barrier_async(self.barrierQueue, ^{
[weakSelf.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
NSOperationQueue *delegateQueue;
//如果unownedSession存在就从它里面获取delegateQueue,否则从ownedSession获取
if (self.unownedSession) {
delegateQueue = self.unownedSession.delegateQueue;
} else {
delegateQueue = self.ownedSession.delegateQueue;
}
//如果存在代理方法执行队列
if (delegateQueue) {
//delegateQueue必须是串行队列 最大并发量为1
NSAssert(delegateQueue.maxConcurrentOperationCount == 1, @"NSURLSession delegate queue should be a serial queue");
[delegateQueue addOperationWithBlock:^{
weakSelf.imageData = nil;
}];
}
//如果ownedSession存在,则手动调用invalidateAndCancel进行任务
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
//finished属性的stter
- (void)setFinished:(BOOL)finished {
//手动触发KVO通知
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
//executing属性的stter
- (void)setExecuting:(BOOL)executing {
//手动触发KVO通知
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
//重写NSOperation方法,标识这是一个并发任务
- (BOOL)isConcurrent {
return YES;
}
#pragma mark NSURLSessionDataDelegate
//收到服务端响应,在一次请求中只会执行一次
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' is an exceptional one
//根据状态码来判断请求的状态
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
//获取要下载图片的长度
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
//设置当前expectedSize
self.expectedSize = expected;
//遍历进度回调块并触发进度回调块
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
//根据response返回的文件大小创建可变data
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//在主线程发送接受response的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
} else {
//进入失败的情况
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
//如果code等于304直接取消下载,否则执行URLSession:task:didCompleteWithError:代理方法
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
///主线程发送结束下载的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
//执行下载失败异常的代码块
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
//执行一些结束下载的操作
[self done];
}
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
//每次收到数据都会触发 可能是多次
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//向可变数据中添加接收到的数据
[self.imageData appendData:data];
//如果调用者配置了需要支持progressive下载,即展示已经下载的部分,并expectedSize返回的图片size大于0
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// Get the image data
NSData *imageData = [self.imageData copy];
// Get the total bytes downloaded
const NSInteger totalSize = imageData.length;
// Get the finish status
//判断是否已经下载完成
BOOL finished = (totalSize >= self.expectedSize);
//如果不存在解压对象就去创建一个新的
if (!self.progressiveCoder) {
// We need to create a new instance for progressive decoding to avoid conflicts
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;
}
}
}
//将imageData转化为image
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
if (image) {
//通过URL获取缓存的key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//对图片进行缩放
image = [self scaledImageForKey:key image:image];
//如果调用者选择了压缩图片,那么在这里执行图片压缩,这里注意,传入的data是一个**,指向指针的指针,要用&data表示
if (self.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
//进行失败回调
[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);
}
}
//如果需要缓存response则做一些处理
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
#pragma mark NSURLSessionTaskDelegate
//下载完成或下载失败时的回调方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
__weak typeof(self) weakSelf = self;
//回到主线程发送下载结束的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
}
});
}
//下载失败则走失败的回调
if (error) {
[self callCompletionBlocksWithError:error];
} else {
//判断下载回调结束块数量是否大于0
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* If you specified to use `NSURLCache`, then the response you get here is what you need.
*/
NSData *imageData = [self.imageData copy];
//如果图片存在
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
*/
//判断调用是否配置了使用缓存,如果是则判断缓存data是否和当前图片一致,如果一致则回调结束
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
} else {
//对图片data解码
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
//根据图片url获得缓存的key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//对图片进行缩放
image = [self scaledImageForKey:key image:image];
BOOL shouldDecode = YES;
// Do not force decoding animated GIFs and WebPs
//针对GIF和WebP图片不做压缩
if (image.images) {
shouldDecode = NO;
} else {
#ifdef SD_WEBP
//sd_imageFormatForImageData分类方法判断是哪种图片格式
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatWebP) {
shouldDecode = NO;
}
#endif
}
//如果是可以压缩的格式,并且调用者也设置了压缩,那么这里开始压缩图片
if (shouldDecode) {
if (self.shouldDecompressImages) {
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
}
} else {
//不存在图片直接回调结束
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
//结束后的操作
[self done];
}
//针对服务器返回的证书进行处理, 需要在该方法中告诉系统是否需要安装服务器返回的证书
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 判断服务器返回的证书是否是服务器信任的
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
if (challenge.previousFailureCount == 0) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
#pragma mark Helper methods
//内部调用一个inline函数
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
//判断是否支持后台下载
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
//回调block
- (void)callCompletionBlocksWithError:(nullable NSError *)error {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
//遍历callbacks代码块数组
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
@end
具体的下载流程用文字来叙述:
1.首先生成继承自NSOperation的SDWebImageDownloaderOperation,配置当前operation。
2.将operation添加到NSOperationQueue下载队列中,添加到下载队列会触发operation的start方法。
3.如果发现operation的isCancelled为YES,说明已经被取消,则finished=YES结束下载。
4.创建NSURLSessionTask执行resume开始下载。
5.当收到服务端的相应时根据code判断请求状态,如果是正常状态则发送正在接受response的通知以及下载进度。如果是304或者其他异常状态则cancel下载操作。
6.在didReceiveData每次收到服务器的返回response时,给可变data追加图片当前下载的data,并汇报下载的进度。
7.在didCompleteWithError下载结束时,如果下载成功进行图片data解码,图片的缩放或者压缩操作,发送下载结束通知。下载失败执行失败回调。
下面画一个SDWebImage下载图片的完整关键流程图,可以让大家有一个更加清晰的认识。
博主水平有限,难免出现纰漏,如有问题还请不吝赐教。