SDWebImage主要的三个功能模块:缓存策略(Cache)、图片的编解码(Decoder)、图片下载(Downloader)
这里总结了一些针对图片下载Downloader模块的读后感,这一模块主要会以分析源码为主。
下载流
将image url作为key,检测正在进行的操作中是否包含key对应的操作。如果不包含或者包含但标记为isFinished,则创建新操作,保存此操作并将其加入到下载队列中。
分析部分属性和方法
SDWebImageDownloader图片下载类,是一个单利。
- 初始化共享的session对象。
- 实现了download queue 、download operation的创建。
.h文件中的下载选项和图片下载方法:
// 下载选项
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
/**
将任务放到低优先级 队列和任务中
*/
SDWebImageDownloaderLowPriority = 1 << 0,
/**
* 类型web加载图片,渐进下载
*/
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* 默认情况下不使用NSURLCache
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* 如果图片是从NSURLCache获取的,那block的回调的image和image data为nil
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* 在IOS4之后,退到后台的的时候会申请一下时间,会继续下载,如果超过了申请时间则取消图片的下载
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* 设置cookie的存储
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* 启用不受信任的SSL证书。一般用于测试,在生产中要小心使用。
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* 高优先级下载
*/
SDWebImageDownloaderHighPriority = 1 << 7,
/**
* 下载大比例的图片
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
// 下载次序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* 默认,所有下载操作都是FIFO
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
* 所有下载操作都是LIFO
*/
SDWebImageDownloaderLIFOExecutionOrder
};
/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
c创建一个异步的下载对象,回调中包含图片下载的结果
*
* @param url 图片链接
* @param options 下载选项
* @param progressBlock 下载progress回调(异步线程)
* @param completedBlock 一旦图片下载完成就会回调,如果下载成功了,image参数就会被设置,如果下载失败了就会回调错误信息,如果没有使用SDWebImageDownloaderProgressiveDownload 渐进下载模式,BOOL finished参数将会被一直设置成YES,如果选择了SDWebImageDownloaderProgressiveDownload选项,在最后一个回调之前image data 都为nil , finished为NO。
*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
.m文件中:
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
// 下载队列
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
// 最后一个操作
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
// 操作对象类
@property (assign, nonatomic, nullable) Class operationClass;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // 保证URLOperations线程安全的锁
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe
// 所有的task将在此session中进行
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
+ (nonnull instancetype)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
}
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
// 是否解压图片
_shouldDecompressImages = YES;
// 执行顺序
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
// 下载队列
_downloadQueue = [NSOperationQueue new];
// 最大并发数
_downloadQueue.maxConcurrentOperationCount = 6;
// 设置队列的名字
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
// 所有的正在下载的任务<key, operationzå>
_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);
_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对象
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
// 在downloader销毁的时候,取消所有下载任务和所有下载操作
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
}
// 返headers
- (nonnull SDHTTPHeadersDictionary *)allHTTPHeaderFields {
LOCK(self.headersLock);
SDHTTPHeadersDictionary *allHTTPHeaderFields = [self.HTTPHeaders copy];
UNLOCK(self.headersLock);
return allHTTPHeaderFields;
}
// 当前下载操作数量
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
// 最大并发数量
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
- (NSURLSessionConfiguration *)sessionConfiguration {
return self.session.configuration;
}
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
} else {
_operationClass = [SDWebImageDownloaderOperation class];
}
}
// 下载图片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
// 具体创建download operation
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// NSURLRequestReloadIgnoringLocalCacheData 忽略本地缓存数据,直接请求服务端
// NSURLRequestUseProtocolCachePolicy 默认的,如果本地没有就进行网络请求,如果有缓存则根据response的Cache-control字段来决定是否进行新的请求
// 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;
// 创建一个请求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
// 设置cookie
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
// 提高请求效率,通常默认情况下请求和响应是顺序的, 也就是说请求–>得到响应后,再请求. 如果将HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求.
request.HTTPShouldUsePipelining = YES;
// 设置请求头
if (sself.headersFilter) {
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) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
// 取消指定token对应的操作
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
NSURL *url = token.url;
if (!url) {
return;
}
// cancel operation、从URLOperations移除这个operation
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (operation) {
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:url];
}
}
UNLOCK(self.operationsLock);
}
// 创建SDWebImageDownloadToken、SDWebImageDownloaderOperation
- (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;
}
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
// 有一种情况,该操作可能被标记为已完成,但没有从“self.URLOperations”中删除。
// There is a case that the operation may be marked as finished, but not been removed from `self.URLOperations`.
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];
// 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.
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
// 暂停
- (void)setSuspended:(BOOL)suspended {
self.downloadQueue.suspended = suspended;
}
// 取消所有的操作
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}
SDWebImageDownloadToken 是一个私有类, 一个SDWebImageDownloadToken实例关联一个download operation,包含图片链接、具备取消下载的作用,在创建一个operation时,都会返回SDWebImageDownloadToken对象。
@interface SDWebImageDownloadToken ()
@property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperationInterface> *downloadOperation;
@end
@implementation SDWebImageDownloadToken
- (void)cancel {
if (self.downloadOperation) {
SDWebImageDownloadToken *cancelToken = self.downloadOperationCancelToken;
if (cancelToken) {
[self.downloadOperation cancel:cancelToken];
}
}
}
@end
SDWebImageDownloaderOperation是NSOperation的子类,实现了SDWebImageDownloaderOperationInterface
接口的实现。
零散的知识点
- NSMutableSet的洪荒之力: 内部实现是一个hash表,所以其保证了数据的唯一性,可以达到去重的效果;内部元素是无序排列的;对内部元素是强引用。
- 你不得不知道的NSURLCache
- 在NSWebImageCompat文件中有一个宏定义:
如果当前是在主线程中,就直接执行操作,不在主线程就转到主线程中执行。
#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
block();\
} else {\
dispatch_async(queue, block);\
}
#endif
-
FOUNDATION_EXPORT
与#define
都是用来定义常量的,FOUNDATION_EXPORT
效率相对较高,前者比较时是用的是指针,后者是比较每一个字符,在宏定义是用多的地方可以考虑使用FOUNDATION_EXPORT
,效率会变高。 - copy操作,在SDWebImage中很多地方使用了copy的操作,如在创建download operation时就将request进行了拷贝。
// 初始化下载操作
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
// copy request
_request = [request copy];
}
return self;
}
-
NSMutableArray、NSMutableDictionary、NSMutableSet是线程不安全的,
-
数组的拷贝,分为NSArray和NSMutiableArray的copy操作
-
block的拷贝
-
数组的valueForKey:方法:
在源码中callbackBlocks
数组中装着key为progress
或completed
的字典,引用着过程block和结果block对象。
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
// 遍历callbackBlocks数组中的每一个字典,将所有key为progress或者completed的字典放在一个数组中返回。
LOCK(self.callbacksLock);
NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
UNLOCK(self.callbacksLock);
// 将数组中的所有[NSNull null]]对象移除,根据地址判断
[callbacks removeObjectIdenticalTo:[NSNull null]];
return [callbacks copy]; // strip mutability here
}
总结下:
如果数组中的元素是字典,valueForKey会遍历数组的所有字典元素,对每一字典使用valueForKey方法,返回一个数组。
如果数组中的元素是普通对象,valueForKey会遍历数组的所有自定义对象,对每一自定义对象使用valueForKey方法,返回一个数组。
如果数组中的元素是数组(子数组的元素是字典),valueForKey会遍历数组的子数组,对子数组中的每一字典使用valueForKey方法,返回一个数组。
另外,可以使用removeObjectIdenticalTo
方法筛选掉数组中的空值。
-
更新
NSOperation
的认识: 对于NSOperation
的认知,它是一个抽象类,是GCD的高级封装,必须通过使用它的子类NSBlockOperation
和NSInvocationOperation
,或是继承NSOperation
,并重写main
方法,要执行NSOperation
操作就需要将它放入NSOperationQueue中,系统会自动安排它自动执行。而异步的SDWebImageDownloaderOperation
重写了start
方法,手动控制finished
、executing
、asynchronous
,具体。
查了下官方解释,中文翻译,有很多需要考虑的地方。 -
NSURLSession、NSURLSessionConfiguration、NSURLSessionTask的一些理解:
- 采用
defaultSessionConfiguration
方法初始化NSURLSessionConfiguration,默认会缓存在磁盘。 - 在SDWebImage中采用一个session实例创建多了task来创建下载任务,这些task的cache和cookie是共享的,并统一控制task的进行,如下方法使得session失效,取消session中所有task的进行。
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
if (self == [SDWebImageDownloader sharedDownloader]) {
return;
}
if (cancelPendingOperations) {
[self.session invalidateAndCancel]; // 取消所有task,并使session失效
} else {
[self.session finishTasksAndInvalidate];// 任务结束时,再使session失效
}
}
网友评论