SDWebImageDownloader
上一篇写了一下sd_setImageWithURL:这个系列方法的大致实现流程,这一篇来详细看一看下载图片时使用的下载器类-SDWebImageDownloader。
让我们先从头文件开始看起:
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
//低优先级的下载
SDWebImageDownloaderProgressiveDownload = 1 << 1,
//逐渐下载,可以实现图片一点点被加载出来的效果
SDWebImageDownloaderUseNSURLCache = 1 << 2,
//开启NSURLCache,默认情况不开启
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
//查找到缓存图片直接返回空值,与SDWebImageDownloaderUseNSURLCache结合使用
SDWebImageDownloaderContinueInBackground = 1 << 4,
//后台下载设置,如果后台app的持续时间到了,这个下载任务也会被取消。
SDWebImageDownloaderHandleCookies = 1 << 5,
//通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES的方式来管理那些储存在NSHTTPCookieStore中的cookies
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
//允许不受信任的SSL证书,测试环境下很有效,在生产环境中需要谨慎使用
SDWebImageDownloaderHighPriority = 1 << 7,
//高优先级下载
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
SDWebImageDownloaderFIFOExecutionOrder,
//先进先出
SDWebImageDownloaderLIFOExecutionOrder
//后进先出
};
对上面这个枚举,我认为是针对整个下载队列的下载顺序,而不是某个下载任务的属性。
extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadStopNotification;
//这里使用const修饰符来定义常量,官方也推荐这样使用而不是#define,[不了解的同学点这里。](http://www.jianshu.com/p/f83335e036b5)。
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
//使用渐进下载时回调的Block receivedSize:已接收的图片数据大小;expectedSize:未接收的图片数据大小
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
//下载完成时回调的Block image:下载的图片;data:图片的Data;error:下载失败时的错误信息;finished:是否下载完成。
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
//一个Header过滤器,会返回过滤之后的headers。
@property (assign, nonatomic) BOOL shouldDecompressImages;
//解压下载后的图片,缺省值是YES,图片过大可能会挤爆内存,根据实际情况设置。
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//最大并发下载数,在SDWebImagePrefetcher中被定义为3。
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//当前并发下载数
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//下载超时时间,缺省值为15.0。
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
//下载队列的执行顺序,默认为先进先出
+ (SDWebImageDownloader *)sharedDownloader;
//返回一个全局的单例下载器对象
@property (strong, nonatomic) NSURLCredential *urlCredential;
//为请求设置默认的URL凭据
/**
* Set username
*/
@property (strong, nonatomic) NSString *username;
/**
* Set password
*/
@property (strong, nonatomic) NSString *password;
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;
//给下载图片的请求过滤header,返回过滤后的header
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
//给每个HTTP的header设置请求头文件,传入nil来取消header
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
//获取HTTPHeader的请求头文件
- (void)setOperationClass:(Class)operationClass;
//给SDWebImageDownloaderOperation设置默认子类。传入nil恢复SDWebImageDownloaderOperation。
- (id )downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
//这个就是核心方法了,用来下载指定URL的图片。下面会详细分析
- (void)setSuspended:(BOOL)suspended;
//控制下载队列暂停与否的属性
/**
* Cancels all download operations in the queue
*/
- (void)cancelAllDownloads;
下面进入.m文件查看downloadImageWithURL:options:progress:completed:方法的实现过程:
- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
[self addProgressCallback:progressBlock
completedBlock:completedBlock forURL:url createCallback:^{....}];
//block中省略号是收起的代码,未展现。由此可见,这个下载图片的方法基本又在addProgressCallback:completedBlock:forURL:createCallback:这个方法中所实现。
return operation;
}
我们进入addProgressCallback:completedBlock:forURL:createCallback:看看它的实现过程:
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)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) {
//URL是作为callbacks(一个字典)的key值,所以不可以为空,如果为空则直接返回。
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
dispatch_barrier_sync(self.barrierQueue, ^{
//这个函数最近正好在书里看到过,是一种在并发队列中插入一小段阻塞当前线程的串行队列方法。
//即前有A、B、C方法并发,中间有dispatch_barrier_sync,其后还有D、E、F并发,程序会先并发ABC、然后执行dispatch_barrier_sync中的方法,并且阻塞当前线程,执行过后再并发DEF。
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// Handle single download of simultaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
//上面的代码可以分析为:通过上层方法的block响应来给url绑定block回调。
if (first) {
createCallback();
//如果是第一次绑定,则创建下载回调,去创建下载任务。
//此时还在dispatch_barrier_sync中,可以保证该URL不会被重复创建下载任务。
}
});
}
下面来看看createCallback里面的执行过程:
NSTimeInterval timeoutInterval = wself.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
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
//为了避免双重缓存,要判断是否设置了SDImageCache,来设置NSURLCache的开关。
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
//设置请求项
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
//设置请求头文件
operation = [[wself.operationClass alloc] initWithRequest:request
inSession:self.session
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
//取出URL对应的block数组
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
//回传进度参数。
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
·· dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
//如果任务完成了,将其从URLCallbacks中删除。
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
//回传下载成功的image
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
//取消下载时将URL对应的所有Block移除。
});
}];
operation.shouldDecompressImages = wself.shouldDecompressImages;
//设置解压选项
if (wself.username && wself.password) {
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}
//用户认证设置
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//设置下载优先级
[wself.downloadQueue addOperation:operation];
//加入下载任务到下载队列中
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
//设置下载队列中的下载顺序
}];
return operation;