SDWebImage图片下载实现分析
SDWebImage主要类的功能如下:
- SDWebImageManager:负责查缓存,下载图片
- SDImageCache:查缓存,分为内存缓存和磁盘缓存两步
- SDWebImageDownloader:图片下载,保存回调block和构造下载operation并加入到queue
- SDWebImageDownloaderOperation:实现图片下载及处理回调
SDWebImageDownloader的实现顺序是
- 保存回调block
- 构建下载operation
- 加入到queue后执行
- 开始下载
- 回调处理
这里主要通过SDWebImageDownloaderOperation分析下载和回调部分,参考代码是SDWebImage的最新版3.8.1
初始化
SDWebImageDownloader会创建SDWebImageDownloaderOperation,在com.hackemist.SDWebImageDownloaderBarrierQueue线程中。
- (id)initWithRequest:(NSURLRequest *)request
inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
if ((self = [super init])) {
//已设置:URL,缓存策略,超时时间,不使用默认cookie,管线化,HeaderFields
_request = request;
_shouldDecompressImages = YES;
//选项:优先级,缓存,后台任务执行,cookie处理以及证书认证几个方面,在创建下载操作的时候可以使用组合的选项来完成一些特殊的需求
_options = options;
//拷贝block 下载中,完成,取消
_progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_executing = NO;
_finished = NO;
_expectedSize = 0;
//NSURLSession 7.0推出替代NSURLConnection
_unownedSession = session;
responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
}
return self;
}
NSURLConnection被替换成了NSURLSession、NSURLSessionConfiguration以及NSURLSessionTask的3个子类:NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。
NSURLSession也是一组相互依赖的类,包括了NSURLRequest与NSURLCache。
一般一个请求创建过程为
- 配置会话属性NSURLSessionConfiguration,可以配置缓存,协议,cookie,以及证书策略(credential policy)
- 由一个NSURLSessionConfiguration对象来初始化一个NSURLSession对象
- 用NSURLSession对象创建NSURLSessionTask
简单说就是属性通过configuration配置,然后初始化session,最后用session创建task来执行。
- configuration可以跨程序共享这些信息给每个session
- session创建时可以指定委托回调对象
- urlrequest在task创建时传入,调用resume方法开始执行请求操作
NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。
<img src="http://img.objccn.io/issue-5/NSURLSession.png" width = "400" />
start
在SDWebImageDownloader内,operation创建成功后被加入到downloadQueue中,轮到该operation执行时就会调用它的start方法
这里获取数据使用的是NSURLSessionDataTask
- (void)start {
//保证只有当前线程执行该方法
@synchronized (self) {
//如果已经取消,清空数据
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
//进入后台时借用系统时间
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
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
//在SDWebImageDownloader的init方法中初始化session,配置了config和委托对象
NSURLSession *session = self.unownedSession;
if (!self.unownedSession) {
//创建sessionConfig,配置会话属性
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* 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.
*/
//初始化session,配置config和委托对象,同SDWebImageDownloader生成的session类似
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
//用session创建task,并传入requst
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
self.thread = [NSThread currentThread];
}
//开始执行task
[self.dataTask resume];
if (self.dataTask) {
//task创建成功,进度回调
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
dispatch_async(dispatch_get_main_queue(), ^{
//通知开始下载,外部UI响应startActivity方法
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
}
else {
//task创建失败,完成回调
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
//停止在后台的执行
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
}
委托方法
由于创建SDWebImageDownloaderOperation时传入了session,session指定了他的持有者SDWebImageDownloader为delegate
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
//通过委托方法传入的task,用taskId找到operation
SDWebImageDownloaderOperation *returnOperation = nil;
for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
//返回对应下载的operation
return returnOperation;
}
但downloader实现的NSURLSessionDataDelegate和NSURLSessionTaskDelegate协议,都会转发给operation来执行。
- NSURLSessionDelegate(继承NSObjectProtocol):处理session级别的任务,比如授权、后台session完成
- NSURLSessionTaskDelegate(继承NSURLSessionDelegate:处理task级别的任务
- NSURLSessionDataDelegate(继承NSURLSessionTaskDelegate:处理data和upload task的专属事件
- NSURLSessionDownloadDelegate(继承NSURLSessionTaskDelegate:处理download task的专属事件,比如下载完成、百分比。
//接受到服务响应时调用的方法
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//设置文件预期大小或取消
}
//接收到服务器返回的数据时调用的方法
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//拼接数据
}
//基于请求缓存数据时调用
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
//缓存
}
在iOS7中,delegate引入了一种全新的处理方式。像这个缓存的回调方法,传入了block,可以让应用在中间进行一些操作之后来决定如何或是否继续执行后面的操作block。
//请求完成时调用的方法(成功或失败)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
//成功block回调
}
//接收服务端挑战
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
//完成认证
}
cancel
取消operation分两种情况
- (void)cancel {
@synchronized (self) {
//请求完成时,会把thread置nil
if (self.thread) {
//请求已经完成
[self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
else {
//请求未完成,执行cancelBlock,cancel掉task,和一些数据清理工作
[self cancelInternal];
}
}
}
以上就是SDWebImageDownloaderOperation主要代码的分析,开始和取消operation。主要是对session的一个运用,以及delegate的处理。
3.8.1是6月7号刚刚发布的版本,分析完这部分对图片的下载过程有个基本的了解,以后写代码多多借鉴。