[iOS] AFNetworking源码学习—下载
github:https://github.com/AFNetworking/AFNetworking
AFNetworking是OC常用的一个网络封装库,3.0以后基于NSSession实现了很多功能,例如网络监控、block监听进度之类的。
首先先来看个文件下载使用案例:
- (void)downLoad{
//1.创建管理者对象
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//2.确定请求的URL地址
NSURL *url = [NSURL URLWithString:@""];
//3.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//下载任务
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
//打印下下载进度
WKNSLog(@"%lf",1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount);
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
//下载地址
WKNSLog(@"默认下载地址:%@",targetPath);
//设置下载路径,通过沙盒获取缓存地址,最后返回NSURL对象
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
return [NSURL fileURLWithPath:filePath];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
//下载完成调用的方法
WKNSLog(@"下载完成:");
WKNSLog(@"%@--%@",response,filePath);
}];
//开始启动任务
[task resume];
}
使用的时候需要先获取manager,然后创建task并开启任务。
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
而AFHTTPSessionManager是继承自AFURLSessionManager的,所以就从AFURLSessionManager创建开始看起吧~
1. AFURLSessionManager init
AFURLSessionManager的init方法如下:
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
// 创建回调所在的NSOperationQueue
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 创建一个NSMutableDictionary,存储task和task对应的delegate
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
// 用于保证NSMutableDictionary多线程读写安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
__weak typeof(self) weakSelf = self;
// 获取session已有的未完成task,然后都给他们设置上delegate,防止backgroundSession可能退出后没有结束继续执行,重新打开app init以后就找不到对应的delegate了。
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
__strong typeof(weakSelf) strongSelf = weakSelf;
for (NSURLSessionDataTask *task in dataTasks) {
[strongSelf addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[strongSelf addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[strongSelf addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
所以这段就是建configuration然后init一些实例变量,最后给session里面所有的任务重置一下delegate。
这里并没有init session啊,因为:
- (NSURLSession *)session {
@synchronized (self) {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
}
}
return _session;
}
为了确保多线程安全,这里用了synchronized,并且采用懒加载的方式创建session,并且只会创建一次。
2. AFURLSessionManager实现的delegate
@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
上面我们看到了session将self(AFURLSessionManager)设为了delegate,它也的确遵从了NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate
这几个协议,它在协议里面做了什么呢?
总的而言分两种,一种是在delegate里面调用相应的block,并发送通知,类似于:
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
另外一种是回调task对应的delegate并且调用self对应的block块,只有7个方法是这么做的,其他几个delegate都是第一种,这种的举例:
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
7个有回调task的delegate的方法分别为:
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
也就是说只有这几个回调方法每个task可以自行进行处理,其他几个回调方法都是和manager绑定的,会执行manager内部的相应block并且发通知。
3. task自身的AFURLSessionManagerTaskDelegate
在上面的代码里面看到每个task都可以设置一个AFURLSessionManagerTaskDelegate,然后再特定的几个回调里面会取出task的delegate并调用([self delegateForTask:task]
)。
先来看下怎么取对应的delegate:
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
所以其实task和它对应的delegate是存在self.mutableTaskDelegatesKeyedByTaskIdentifier里面的,key是task的taskIdentifier,value就是delegate。
由于NSMutableDictionary不是线程安全的,所以这里需要用NSLock防止多线程同时操作。
这里其实也涉及了第一部分里面的一个设置:
为什么init里面self.operationQueue.maxConcurrentOperationCount设为1?
因为回调所在的线程是operationQueue,而回调里面有的时候是要removeTaskDelegate的,也就是会操作这个self.mutableTaskDelegatesKeyedByTaskIdentifier,那么由于它是加锁的,如果有用这个字典,就和单线程一样了,于是用maxConcurrentOperationCount限制为串行队列的感觉。
那么是在哪里把delegate加入到字典里面的呢?
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
另外在加delegate的时候还要给他们加observer:
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
这个observer是干啥的呢?
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
首先在task resume的时候它会抛出AFNSURLSessionTaskDidResumeNotification通知,然后manager的taskDidResume:会执行:
- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}
也就是说它判断了一下task的taskDescription属性是不是和自己的一致以后,就又发了一个通知AFNetworkingTaskDidResumeNotification。
那为啥要判断taskDescription呢?
因为每个task无论是不是这个afmanager创建的一旦resume都会发AFNSURLSessionTaskDidResumeNotification通知(当afmanager交换了方法以后,这个之后会说),但是manager不关心自己创建的task之外的方法的resume,所以会通过taskDescription筛选出自己的task。
这里巧用了taskDescription,毕竟如果它循环dict也可以找到task,但是如果用taskDescription给每个task加个tag就节约了保存task以及查找的时间。
manager的self.taskDescriptionForSessionTasks其实就是自己的内存地址。
- (NSString *)taskDescriptionForSessionTasks {
return [NSString stringWithFormat:@"%p", self];
}
task的taskDescription是在add的时候加上去的:
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
当设置delegate的时候先创建一个AFURLSessionManagerTaskDelegate,然后把他的progress和complete block设置上,并且manager也设进去。这里之后可以看下delegate内部定义,manager是弱引用,所以虽然manager通过self.mutableTaskDelegatesKeyedByTaskIdentifier持有delegate,但delegate并没有持有manager。
然后调用setDelegate:
,即之前看过的将delegate和相应task加入到NSMutableDictionary以及给task加observer。
这里终于引入了正题AFURLSessionManagerTaskDelegate:
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
- (instancetype)initWithTask:(NSURLSessionTask *)task;
@property (nonatomic, weak) AFURLSessionManager *manager;
@property (nonatomic, strong) NSMutableData *mutableData;
@property (nonatomic, strong) NSProgress *uploadProgress;
@property (nonatomic, strong) NSProgress *downloadProgress;
@property (nonatomic, copy) NSURL *downloadFileURL;
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics;
#endif
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
@end
除了进度block,它只有一些NSProgress以及弱引用了manager,还记录了url,以及用于保存接收下来的数据的mutableData,还有sessionTaskMetrics(一个用于保存response时间以及redirect count的东东)。对外只提供了initWithTask:
- (instancetype)initWithTask:(NSURLSessionTask *)task {
self = [super init];
if (!self) {
return nil;
}
// 初始化_mutableData以及两个NSProgress
_mutableData = [NSMutableData data];
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
// 这里都用了weak引用,这样progress的block就不持有delegate了,防止了内存泄漏
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
// 给NSProgress设置可以取消、暂停、继续,并且当Progress被操作暂停时,task也暂停。
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
#if AF_CAN_USE_AT_AVAILABLE
if (@available(iOS 9, macOS 10.11, *))
#else
if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
{
progress.resumingHandler = ^{
[weakTask resume];
};
}
// 最后给progress加observer,监听NSProgress的fractionCompleted属性,也就是完成的百分比
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
初始化delegate的时候监听了两个progress变量的fractionCompleted属性,当它们变化以后会触发:
#pragma mark - NSProgress Tracking
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
也就是说如果我们修改NSProgress的进度,delegate的block就会回调,并且把新的fractionCompleted进度百分比传给block。
然后AFURLSessionManagerTaskDelegate还实现了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate。
AFURLSessionManagerTaskDelegate还记得manager也遵循了这些delegate,并且把7个方法转调了task的delegate的方法么,其实就是这7个。
- 我们看几个重要的方法吧:
NSURLSessionDataDelegate - didReceiveData
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
[self.mutableData appendData:data];
}
这个回调是接收数据过程中不断回调的,会把新接收到的data传入,delegate将每次接收到的append给self.mutableData,并且更改了self.downloadProgress.completedUnitCount以及self.downloadProgress.totalUnitCount,于是downloadProgress的fractionComplete就自动更改了,会触发KVO调用self.downloadProgressBlock。
NSURLSessionDataDelegate - didSendBodyData
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}
这个是上传过程中会不断回调的一个函数,和didReceiveData类似,只是因为上传不需要记录data,所以只要改一下updateProgress触发self.uploadProgressBlock就可以啦。
NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
self.downloadProgress.totalUnitCount = expectedTotalBytes;
self.downloadProgress.completedUnitCount = fileOffset;
}
和NSURLSessionDataDelegate的两个delegate相似,也是设置progress触发KVO的。
但是这两个方法并不提供data哦,所以不用append data给self.mutableData。(download task可以用resume data)
在finish的时候会调用:
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
// finish以后会把url置空
self.downloadFileURL = nil;
// 如果有设置downloadTaskDidFinishDownloading block,则会执行拿到要把数据move到哪个目录
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
// 移动数据成功后发个通知
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
此时会把self.downloadFileURL置空,并且通过self.downloadTaskDidFinishDownloading得到要把下载好的tmp数据放到哪里存起来,存储位置放入self.downloadFileURL
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
可以看到这个block会接收task以及location,然后返回一个NSURL,也就是文件位置。
NSURLSessionTaskDelegate - didFinishCollectingMetrics
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
self.sessionTaskMetrics = metrics;
}
#endif
这里只是将NSURLSessionTaskMetrics存起来给实例变量,类里面没怎么用,如果外面用可以读,内容大概就是:
(Task Interval) <_NSConcreteDateInterval: 0x600000f50080> (Start Date) 2019-10-22 02:02:08 +0000 + (Duration) 0.193185 seconds = (End Date) 2019-10-22 02:02:08 +0000
(Redirect Count) 0
(Transaction Metrics) (Request) <NSURLRequest: 0x600000d34a50> { URL: https://api.app.net/stream/0/posts/stream/global }
(Response) (null)
(Fetch Start) 2019-10-22 02:02:08 +0000
(Domain Lookup Start) (null)
(Domain Lookup End) (null)
(Connect Start) (null)
(Secure Connection Start) (null)
(Secure Connection End) (null)
(Connect End) (null)
(Request Start) 2019-10-22 02:02:08 +0000
(Request End) 2019-10-22 02:02:08 +0000
(Response Start) 2019-10-22 02:02:08 +0000
(Response End) (null)
(Protocol Name) (null)
(Proxy Connection) NO
(Reused Connection) YES
(Fetch Type) Unknown
其实这个就是保存一下类似命令行ping一个IP以后的各种时间以及redirect。
NSURLSessionTaskDelegate - didCompleteWithError
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// manager是weak属性的,这里为了在范围内不让系统回收manager,会先转成strong持有
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
// 获取manager的responseSerializer用于parse response
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//用局部变量保存self.mutableData,然后去除self.mutableData的持有
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
#if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
if (self.sessionTaskMetrics) {
userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
}
}
#endif
// 如果self.downloadFileURL不为空,说明已经走过NSURLSessionDownloadDelegate的didFinishDownloadingToURL了,已经转存了下载好的文件了
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
// 如果没有转存好file,就把接收到的data都放入userinfo给外面
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
// 如果有error就异步抛到group,发一个异步的AFNetworkingTaskDidCompleteNotification通知并且把空的responseObject和error传回completionHandler
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
// 如果没有error
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
// 用manager.responseSerializer给response parse一下然后得到responseObject
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
// 最后同样是将response和error传回completionHandler,然后发送AFNetworkingTaskDidCompleteNotification通知
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
所以这个delegate内部已经用manager的responseSerializer将response解析好了,放入了responseObject传回completionHandler了,但如果有downloadFileURL那么responseObject就会被设置为存储位置downloadFileURL。
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);
外部还可以通过监听AFNetworkingTaskDidCompleteNotification拿到userinfo,然后通过各种key拿到sessionTaskMetrics、responseSerializer、assetPath、data之类的。
4. manager如何创建task
先来看下download task怎么创建滴:
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
__block NSURLSessionDownloadTask *downloadTask = nil;
url_session_manager_create_task_safely(^{
downloadTask = [self.session downloadTaskWithRequest:request];
});
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
return downloadTask;
}
这里看到其实就是通过session来创建了一个task,只是放入了url_session_manager_create_task_safely里面。最后再add一下task的delegate。
这里用sync同步的方式创建task,也是因为如果不用同步,下一步到抛到addDelegate的时候需要给delegate以及task建立映射,如果task还是nil,建立映射也没有意义,所以必须等待task创建完毕才能执行下一步。
但是url_session_manager_create_task_safely是啥嘞:
static void url_session_manager_create_task_safely(dispatch_block_t _Nonnull block) {
if (block != NULL) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
}
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
#endif
也就是说在ios8.0以下的时候,会通过同步抛入url_session_manager_creation_queue创建一个task,如果ios8.0以上就直接创建。
原因其实是:iOS 8修复了一个bug,多线程下创建的task小概率拥有同样的task identifier。但是AFNetworking使用 task identifier作为manager的mutableTaskDelegatesKeyedByTaskIdentifier的key,需要task identifier唯一。
url_session_manager_creation_queue其实就是创建了一个串行队列:
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
除此之外,
manager内部通过dispatch_once创建了static的:
- url_session_manager_creation_queue 创建串行queue
- url_session_manager_processing_queue 创建并行queue
- url_session_manager_completion_group 创建任务完成后completionHandler抛入的group
现在来看下创建完task以后的addDelegate做了什么:
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
if (destination) {
delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
return destination(location, task.response);
};
}
downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:downloadTask];
delegate.downloadProgressBlock = downloadProgressBlock;
}
我们之前也看了addDelegateForDataTask,其实addDelegateForDownloadTask和他差不多,都是先init一个AFURLSessionManagerTaskDelegate,然后将delegate的completionHandler、uploadProgressBlock 、downloadProgressBlock、taskDescription之类的设好。最后通过setDelegate建立映射以及observer。
addDelegateForDownloadTask比其他两个多了一个destination,也就是下载后的文件要放在哪里。通过设置delegate.downloadTaskDidFinishDownloading即可,接收location和task.response参数,返回一个NSURL,即文件保存地址。AFURLSessionManagerTaskDelegate会自动在下载结束后将文件移动到这个URL的。
所以其实task启动以后,进度之类的先是回掉了session的delegate-AFURLSessionManager,然后AFURLSessionManager会把一些特定的方法转调task的delegate-AFURLSessionManagerTaskDelegate,然后通过设置AFURLSessionManagerTaskDelegate的成员变量,引发KVO监听,自动回调AFURLSessionManagerTaskDelegate的progressBlock以及completionBlock。
5. resume data
download task是可以通过resume data创建的也是:
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
__block NSURLSessionDownloadTask *downloadTask = nil;
url_session_manager_create_task_safely(^{
downloadTask = [self.session downloadTaskWithResumeData:resumeData];
});
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
return downloadTask;
}
那么如何获取resumeData呢?
在AFURLSessionManagerTaskDelegate 的didCompleteWithError里面将error回传给了completionHandler,所以可以在completionHandler通过error的userinfo来拿到resume data。
if (error) {
if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
NSData *data = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
self.resumeData = data;
// todo 保存resume data到沙盒
}
}
到此几乎下载是如何实现的就写完啦,之后会搞一下其他的几个模块以及一些好的地方吧~
参考:
https://www.jianshu.com/p/ddf79f0763a7
https://www.jianshu.com/p/856f0e26279d
https://www.jianshu.com/p/170243957f75
使用:https://www.jianshu.com/p/40b076870460
http://ju.outofmemory.cn/entry/295593