根据我阅读源码的习惯,我会先阅读比较核心的,并且没有或者较少引入其他文件的类。所以,就让我们先从SDWebImageDownloaderOperation说起吧。
在iOS9.0之后,苹果推出了NSUrlSession,并推荐开发者用NSUrlSession替代NSUrlConnection。如果我们自己用NSUrlSession来请求图片,主要有:
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pic27.nipic.com/20130329/890845_115317964000_2.jpg"]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
delegateQueue:为执行代理方法的队列,不指定session默认会创建一个serial operation queue
在回调的代理方法中处理数据
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.imageData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
UIImage *image = [UIImage imageWithData:self.imageData];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
}
SDWebImageDownloaderOperation是NSOperaion的子类,就是用来封装上面的下载操作
先来看看SDWebImageDownloaderOperation.h
文件一开始向外部暴露了四个通知:
//下载开始
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
//接受到服务器响应
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
//下载停止
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
//下载结束
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
在UIView+WebCache分类中,就是对SDWebImageDownloadStartNotification和SDWebImageDownloadFinishNotification通知的监听来添加和移除UIActivityIndicatorView 。
在SDWebImageDownloaderOperation.h头文件中,定义了SDWebImageDownloaderOperationInterface 协议。我们可以继承NSOperation同时遵守SDWebImageDownloaderOperationInterface协议来自定义下载操作类。
@protocol SDWebImageDownloaderOperationInterface<NSObject>
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
//为下载操作添加下载中和下载完成的回调
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//是否需要对下载的图片进行预解码
- (BOOL)shouldDecompressImages;
- (void)setShouldDecompressImages:(BOOL)value;
//https证书
- (nullable NSURLCredential *)credential;
- (void)setCredential:(nullable NSURLCredential *)value;
//取消下载操作
- (BOOL)cancel:(nullable id)token;
@end
在SDWebImageDownloaderOperation类的私有拓展中包含以下属性:
//包含下载操作回调block的数组
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
//下载运行中
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
//下载结束
@property (assign, nonatomic, getter = isFinished) BOOL finished;
//拼接的数据
@property (strong, nonatomic, nullable) NSMutableData *imageData;
//使用NSUrlCache缓存的数据
@property (copy, nonatomic, nullable) NSData *cachedData;
//外部注入的NSURLSession,因为在SDWebImageDownloader中已经对这个session强引用,所有使用weak
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
//如果外部没有注入session,内部会自己实例化一个session,并负责这个session的生命周期
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;
//通过UrlRequest生成的dataTask请求任务
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
//为保证在多线程环境下操作callbackBlocks的数据安全提供的锁
@property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock;
//对图片进行解码的队列
@property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue;
#if SD_UIKIT
//app退到后台后向UIApplication注册的后台任务标识,可以获得一些额外的下载时间
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
//对图片进行渐进式解码的解码器
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
下面来看一下SDWebImageDownloaderOperation的构造函数:
- (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;
_callbacksLock = dispatch_semaphore_create(1);
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
为SDWebImageDownloaderOperation添加回调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];
LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks];
UNLOCK(self.callbacksLock);
return callbacks;
}
progressBlock和completeBlock最终会以@{@"progress ":progressBlock,@"completed":completedBlock}的形式添加callbackBlocks数组。
下面是通过字符串来获取所有的progressBlock或者所有的completedBlock:
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
LOCK(self.callbacksLock);
//NSArray对KVC的拓展,所有元素都会执行valueForKey: 方法,并把结果添加到数组返回。如果valueForKey:返回nil,则会把NSNull添加进数组。
NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
UNLOCK(self.callbacksLock);
// We need to remove [NSNull null] because there might not always be a progress block for each callback
[callbacks removeObjectIdenticalTo:[NSNull null]];
return [callbacks copy]; // strip mutability here
}
下面的代码是移除某一组回调的block:
- (BOOL)cancel:(nullable id)token {
BOOL shouldCancel = NO;
LOCK(self.callbacksLock);
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
//当所有的回调都被移除,就没有下载的必要了。
shouldCancel = YES;
}
UNLOCK(self.callbacksLock);
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
下面来看看[self cancel]做了什么:
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
//把operation置为取消状态
[super cancel];
if (self.dataTask) {
//取消下载任务
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
- (void)reset {
LOCK(self.callbacksLock);
//移除所有回调
[self.callbackBlocks removeAllObjects];
UNLOCK(self.callbacksLock);
//释放请求任务
self.dataTask = nil;
if (self.ownedSession) {
//释放内部生产的session
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
主要就是对取消之后资源的管理和状态的维护。
下面来看看最主要的start方法,当SDWebImageDownloaderOperation添加到并发队列,就会调用start方法。
- (void)start {
@synchronized (self) {
//如果operatin被取消,置位finished标志,并释放资源
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#flag:注册后台任务
#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)];
//当app即将退到后台,想UIApplication注册一个后台执行的任务,以获取额外的操作时间
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
//当额外的时间后仍没有完成下载任务,则取消掉任务
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
if (!session) {
//如果外部没有注入session,内部负责生成并管理一个session
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
//SDWebImage默认的超时时间为15秒
sessionConfig.timeoutIntervalForRequest = 15;
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
@synchronized (URLCache) {
//根据请求拿到缓存的响应
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
//拿到NSUrlCache缓存的响应对应的数据
self.cachedData = cachedResponse.data;
}
}
//实例化下载任务
self.dataTask = [session dataTaskWithRequest:self.request];
//设为运行中
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];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//下载开始通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
//如果下载任务实例化失败,则以错误的状态调用completedBlock回调
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
return;
}
#从"flag:注册后台任务"到这行代码区间的代码,就是app退到后台向UIApplication注册的backgroundTask
#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
}
执行start后下载开始,接下来就是监听NSURLSessionDelegate 的代理方法来处理数据了。
//接受到服务器响应数据
- (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;
self.response = response;
NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
BOOL valid = statusCode < 400;
//'304 Not Modified'代表资源未改变,本地又没有NSUrlCache缓存的数据,当成非法请求看待
if (statusCode == 304 && !self.cachedData) {
valid = NO;
}
if (valid) {
//如果响应有效,则回调处理所有progressBlock
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
} else {
//取消后续下载
disposition = NSURLSessionResponseCancel;
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
if (completionHandler) {
completionHandler(disposition);
}
}
//接受到下载的数据包
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
if (!self.imageData) {
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
}
//数据拼接
[self.imageData appendData:data];
//如果配置渐进式的下载,则对当前数据进行渐进式解码
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
__block NSData *imageData = [self.imageData copy];
const NSInteger totalSize = imageData.length;
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, ^{
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)}];
}
//每次解码调用completedBlock进行UIView的渲染
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
});
}
//回调处理所有progressBlock
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;
__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];
}
});
}
//下载出错,以错误的状态调用completedBlock,同时把operation设置finished状态
if (error) {
[self callCompletionBlocksWithError:error];
[self done];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
__block NSData *imageData = [self.imageData copy];
if (imageData) {
//这边为什么下载数据等于缓存数据要以nil调用completedBlock不是很懂,希望了解的人告知一二
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
[self done];
} else {
// 在解码队列上异步解码图片
dispatch_async(self.coderQueue, ^{
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
BOOL shouldDecode = YES;
// GIFs and WebPs不支持解码
if (image.images) {
shouldDecode = NO;
} else {
#ifdef SD_WEBP
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)}];
}
}
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
//当下载的图片大小为0,以错误的状态回调completedBlock
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
//回调函数completedBlock处理下载的图片
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
[self done];
});
}
} else {
//当下载的图片为空,以错误的状态回调completedBlock
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
[self done];
}
} else {
[self done];
}
}
}
细心的你可能会发现,这些代理方法只是对ownedSession而言,那么对于unownedSession 如何监听呢?unownedSession代理方法由SDWebImageDownloader监听,然后一一转发到SDWebImageDownloaderOperation的代理方法中进行处理。这些我们在SDWebImageDownloader的解析中将会看到。
以上关于解码的部分只是一笔略过,以后会专门写一篇。
网友评论