属性配置
SDWebImageDownloader就是图片下载器,利用其单例对下载进行属性配置,主要包括3个方面:
- 下载器选项 SDWebImageDownloaderOptions
- HTTP头部
- 图片是否解压、下载顺序、最大并发数、认证配置、下载超时等
在以下两个方法中配置:
- (id)init;
- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
下载器类型枚举SDWebImageDownloaderOptions
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
// 图像下载置于低优先队列中
SDWebImageDownloaderLowPriority = 1 << 0,
// 分进度下载图片,可实现将图片一点点显示出来
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* By default, request prevent the use of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
// 默认情况下request不使用NSURLCache。使用该选项,默认的缓存策略使用NSURLCache
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
// 如果image从NSURLCache中读取,则回调中的image/imageData为nil
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
// 在iOS 4+中,app在后台运行同时也可以下载图片。该操作通过系统申请额外的时间来完成后台下载。如果后台任务过期,操作将会被取消。
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
// 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES来处理存储在NSHTTPCookieStore中的cookies
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
// 允许非信任的SSL证书。主要用于测试,发布版本中谨慎使用。
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* Put the image in the high priority queue.
*/
// 将图像下载置于高优先队列中
SDWebImageDownloaderHighPriority = 1 << 7,
};
图片下载执行方式枚举SDWebImageDownloaderExecutionOrder
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* Default value. All download operations will execute in queue style (first-in-first-out).
*/
// 默认下载方式:先进入队列先下载
SDWebImageDownloaderFIFOExecutionOrder,
/**
* All download operations will execute in stack style (last-in-first-out).
*/
// 后进入队列优先下载
SDWebImageDownloaderLIFOExecutionOrder
};
相关属性说明
// 对已下载或缓存的图片进行解压会提高性能,但会消耗一些内存。默认为YES
@property (assign, nonatomic) BOOL shouldDecompressImages;
// 下载操作队列
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
// 上一次添加的操作
@property (weak, nonatomic) NSOperation *lastAddedOperation;
// 图片下载类
@property (assign, nonatomic) Class operationClass;
// URL回调字典,以URL为key、装有URL下载的进度block和完成block的数组为value
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
// HTTP请求头
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
// 该队列用于处理所有下载操作的网络响应序列化任务
// 为了保证线程安全,所有增改回调集合URLCallbacks的操作使用dispatch_barrier_sync放入队列barrierQueue中,而查询URLCallbakcs的操作只需使用dispatch_sync放入队列barrierQueue中。
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
// 使用NSURLSession进行数据传输,参考:http://www.cocoachina.com/industry/20131106/7304.html
@property (strong, nonatomic) NSURLSession *session;
判断程序中是否含有该类NSClassFromString
包括两种写法:
- id myObj = [[NSClassFromString(@"SDNetworkActivityIndicator") alloc] init];
- id myObj = [[SDNetworkActivityIndicator alloc] init];
前面一种写法更安全,没有该类只会返回一个空对象,使用NSClassFromString进行不确定的类的初始化。
- NSClassFromString弱化连接,并不会把没有的Framework也link到程序中。
- NSClassFromString不需使用import,因为类是动态加载的,只要存在就可以加载。因此如果你的toolchain中没有某个类的头文件定义,但你确定这个类可用,也可以用这种方法。
barrierQueue初始化说明
barrierQueue初始化说明:
// DISPATCH_QUEUE_CONCURRENT:不等待现在执行中处理结束,使用多个线程处理多个任务
// 注意:SDImageCache中队列初始化创建的队列ioQueue类型为DISPATCH_QUEUE_SERIAL,即等待现在执行中处理结束,使用一个线程。一张图片的的输入输出(读取 或 删除)只有一个线程操作,保证线程安全。
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
代码注释
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h"
#import
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
@interface SDWebImageDownloader ()
// 下载操作队列
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
// 上一次图片下载操作
@property (weak, nonatomic) NSOperation *lastAddedOperation;
// 图片下载操作类
@property (assign, nonatomic) Class operationClass;
// URL回调字典,以URL为key、装有URL下载的进度block和完成block的数组为value
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
// HTTP请求头
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
// 该队列用于处理所有下载操作的网络响应序列化任务
// 为了保证线程安全,所有增改回调集合URLCallbacks的操作使用dispatch_barrier_sync放入队列barrierQueue中,而查询URLCallbakcs的操作只需使用dispatch_sync放入队列barrierQueue中。
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
// 使用NSURLSession进行数据传输,参考:http://www.cocoachina.com/industry/20131106/7304.html
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
#pragma mark -- 初始化
/**
* 注意initialize方法里面不可进行太过复杂的操作
*/
+ (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
// 判断程序中是否含有该类NSClassFromString:没有该类只会返回一个空对象,使用NSClassFromString进行不确定的类的初始化
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];
}
}
/**
* 图片下载器单例
*
* @return <#return value description#>
*/
+ (SDWebImageDownloader *)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
/**
* 初始化
*
* @return <#return value description#>
*/
- (id)init {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_URLCallbacks = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
// DISPATCH_QUEUE_CONCURRENT:不等待现在执行中处理结束,使用多个线程处理多个任务
// 注意:SDImageCache中队列初始化创建的队列ioQueue类型为DISPATCH_QUEUE_SERIAL,即等待现在执行中处理结束,使用一个线程。一张图片的的输入输出(读取 或 删除)只有一个线程操作,保证线程安全。
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = _downloadTimeout;
/**
* Create the session for this task
* 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.
*/
// delegateQueue设置为nil(设置delegate在哪个OperationQueue回调):没太明白???
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
}
return self;
}
/**
* 销毁操作:销毁会话任务、队列操作
*/
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
#pragma mark -- 属性设置与获取
/**
* 设置HTTPHeader
*
* @param value 值
* @param field 键
*/
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
/**
* 获取HTTPHeader中键所对应的值
*
* @param field 键
*
* @return 值
*/
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
return self.HTTPHeaders[field];
}
/**
* 设置队列中最大的并发操作数
*
* @param maxConcurrentDownloads <#maxConcurrentDownloads description#>
*/
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
/**
* 获取队列中的并发操作数
*
* @return <#return value description#>
*/
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
/**
* 获取队列中的最大并发操作数
*
* @return <#return value description#>
*/
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
- (void)setOperationClass:(Class)operationClass {
_operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
}
#pragma mark -- 图片下载操作
/**
* 根据URL下载图片
*
* @param url 图片的URL
* @param options 下载器类型SDWebImageDownloaderOptions
* @param progressBlock 图片下载进度回调
* @param completedBlock 图片下载完成回调
*
* @return <#return value description#>
*/
- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
// 调用另一方法将:并在创建回调的block中创建新的操作(即第一次下载),配置之后将其放入downloadQueue操作队列中
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL: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
// 创建请求对象,并根据options参数设置其属性。为了避免潜在的重复缓存(NSURLCache+SDImageCache),如果没有明确告知,则禁用图片请求的缓存操作
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
// 创建SDWebImageDownloaderOperation操作对象,并进行配置
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, ^{
// 根据URL键获取对应的值(进度回调、完成回调的数组)
callbacksForURL = [sself.URLCallbacks[url] copy];
});
// 处理回调进度
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;
// 下载完成,则从回调字典中删除url。这里必须使用dispatch_barrier_sync
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
// 下载取消,则从回调字典中删除url。感觉这里也可以使用dispatch_barrier_sync。
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
// 是否解压图片
operation.shouldDecompressImages = wself.shouldDecompressImages;
// operation的认证配置
if (wself.urlCredential) {
operation.credential = wself.urlCredential;
} else 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
// 如果是LIFO顺序,则将新的操作作为原队列中的最后一个操作的依赖,然后将新操作设置为最后一个操作。
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];
return operation;
}
/**
* 添加设置回调:将请求的信息存入下载器
*
* @param progressBlock 下载进度回调
* @param completedBlock 下载完成回调
* @param url 下载URL
* @param 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.
// URL作为调用字典URLCallbacks的键值,不能为空。如若为空的话,会立即调用完成block,没有图像或者数据。
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
// 以dispatch_barrier_sync:保证同一时间只有一个线程能对URLCallbacks进行操作
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
// 如果URLCallbacks中不含有url,则判断为第一次操作
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// 同步下载中相同的URL只进行一个下载:Handle single download of simultaneous download request for the same URL
// URLCallbacks字典:以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;
// 第一次下载回调
if (first) {
createCallback();
}
});
}
- (void)setSuspended:(BOOL)suspended {
[self.downloadQueue setSuspended:suspended];
}
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}
#pragma mark -- Helper methods
- (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;
}
#pragma mark -- NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}
#pragma mark -- NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}
@end