白水

iOS 框架详解—「AFURLSessionManager 网络通信核心」

引导


AFNetWorking 基本是 iOS 开发中使用网络通信框架的标配,这个框架本身比较庞大,也很复杂,但是使用起来非常非常简单。极大地提高开发效率,让我们更加专注于业务逻辑实现。

本篇文章主要从【AFURLSessionManager 网络通信核心】学习总结,该模块学习将续更 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@白开水ln原著;

Source Code【源码学习】


开始学习 AFURLSessionManager 这个类,主要提供了数据的请求、上传和下载功能。

该类代码太长,我们将分节点学习。

.h 中注释 介绍:AFURLSessionManager 创建并且管理一个 NSURLSession 对象,这个对象是基于一个规定的 NSURLSessionConfiguration 对象,遵循协<NSURLSessionTaskDelegate>, <NSURLSessionDataDelegate>, <NSURLSessionDownloadDelegate>, 和 <NSURLSessionDelegate>

Attribute【属性部分】


1
2
3
4
5
6
7
8
9
10
11
/**
The managed session.
*/
// 会话对象
@property (readonly, nonatomic, strong) NSURLSession *session;

/**
The operation queue on which delegate callbacks are run.
*/
// 代理回调所运行的 操作队列
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;

Session 就是 该类要管理的 NSURLSession 对象,
operationQueue是操作队列,当代理回调的时候运行。

@name Getting Session Tasks【获得会话任务】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
The data, upload, and download tasks currently run by the managed session.
*/
// 任务(抽象类,本身不能使用,只能使用它的子类)
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;

/**
The data tasks currently run by the managed session.
*/
// (数据)任务,一般用来处理网络请求,如(GET|POST)请求。或 用于(断点下载|支持离线)
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;

/**
The upload tasks currently run by the managed session.
*/
// (上传)任务,用于处理上传请求
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;

/**
The download tasks currently run by the managed session.
*/
// (下载)任务,用于处理下载请求
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;

通过这四个属性,我们分别可以拿到总的任务集合(包括上传和下载任务)、数据任务集合、上传任务集合和下载任务集合。

@name Additional Functionality【额外的功能】

1
2
3
4
5
6
7
8
9
10
11
/**
The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified.
*/
// 安全相关(策略)
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

/**
The network reachability manager. `AFURLSessionManager` uses the `sharedManager` by default.
*/
// 网络状态检测管理者
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;

securityPolicy是用于处理网络连接安全处理策略;reachabilityManager是检测网络状态的检测器。

@name Working Around System Bugs【工作系统错误】

1
2
3
4
5
6
7
8
/**
Whether to attempt to retry creation of upload tasks for background sessions when initial call returns `nil`. `NO` by default.

@bug As of iOS 7.0, there is a bug where upload tasks created for background tasks are sometimes `nil`. As a workaround, if this property is `YES`, AFNetworking will follow Apple's recommendation to try creating the task again.

@see https://github.com/AFNetworking/AFNetworking/issues/1675
*/
@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions;

注意:
注释里面写到,在iOS7中存在一个bug,在创建后台上传任务时,有时候会返回nil。作为一个修补方案,如果设置这个属性为YES, AFNetworking将会遵照苹果的建议,在创建失败的时候,会重新尝试创建,次数默认为3次。所以你的应用如果有在后台上传的情况的话,记得将该值设为YES,避免出现上传失败的问题。

属性看完,接着到初始化方法了:

@name Initialization【初始化】

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
NS_DESIGNATED_INITIALIZER
意为“被设计的初始化器”,“被指定的初始化方法”
*/
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

/**
选择性地取消挂起的task

设置 YES 的话,会在主线程直接关闭掉当前会话
设置 NO 的话,会等待当前task结束后再关闭.
*/
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;

可以看到,第一个方法为初始化方法,第二个方法为选择性的取消挂起的session task,只需要传入某个挂起的task为参数,便可取消该task。

接下来到了核心方法部分了:

@name Running Data Tasks【运行数据的任务】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
下面两种是不同的数据请求方法
request 则是你发出的HTTP请求,
uploadProgressBlock 和 downloadProgressBlock 则是在如果上传和下载进度有更新的情况下才会调用,
completionHandler 就是在请求完成之后返回的内容(错误信息当请求失败的时候,error有值)
*/
// DEPRECATED_ATTRIBUTE 标识符的意思是慢慢弃用的属性或接口,如果我们使用了,在xcode中就会出现警告⚠️信息。
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable
responseObject, NSError * _Nullable error))completionHandler DEPRECATED_ATTRIBUTE;


- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable
responseObject, NSError * _Nullable error))completionHandler;

@name Running Upload Tasks【运行上传任务】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
下面三种不同的数据上传方法
第一种是 通过fileURL(需要上传的本地文件URL路径)上传,
第二种是 通过bodyData(需要上传的HTTP body体的数据),
第三种是 使用流(输出流)请求的方法,在使用该方法的时候,一定要设置setTaskNeedNewBodyStreamBlock回调,
否则session没办法在重新发送steam的时候找到数据源。
*/
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable
responseObject, NSError * _Nullable error))completionHandler;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(nullable NSData *)bodyData
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable
responseObject, NSError * _Nullable error))completionHandler;

- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable
responseObject, NSError * _Nullable error))completionHandler;

@name Running Download Tasks【运行下载任务】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
下面是两种下载方法,
第一种 Request 是通过HTTP请求方式下载,
第二种 ResumeData 则是通过之前的下载数据来恢复下载,
destination在下载的过程中文件会先存放在一个临时的位置,

注意:该方法内部已经实现了边接受数据边写入沙盒(tmp临时文件目录)的操作,
等到下载完成之后,需要剪切文件,把它移动到我们指定的位置。
*/
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath,
NSError * _Nullable error))completionHandler;

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath,
NSError * _Nullable error))completionHandler;

@name Getting Progress for Tasks【获得进度的任务】

1
2
3
4
5
// 获取上传进度
- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;

// 获取下载进度
- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;

再接着就是 Setting Session Delegate Callbacks 方法了,它们 NSURLSession 的【DownloadTask、DataTak】一一对应的。如果需要可参考 NSURLSession活用。
在这里不作过多说明。就列举几个

1
2
3
4
5
6
/**
请求完成 或者 失败的时候调用
注意:如果创建句柄(NSFileHandle),要记得在完成方法里 关闭句柄置空)
*/
- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session,
NSURLSessionTask *task, NSError * _Nullable error))block;

@name Setting Data Task Delegate Callbacks【设置数据任务委托回调】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
1.接收到服务器的时候,回调,传给系统
completionHandler(NSURLSessionResponseAllow);
NSURLSessionResponseAllow = 1, 接收数据
*/
- (void)setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)
(NSURLSession *session, NSURLSessionDataTask *dataTask,
NSURLResponse *response))block;

/**
2.接收到服务器返回的数据,会调用多次(Data可能是部分)

// 使用一个可变的data,对接收到的data进行拼接,直到是完成
[self.fileData appendData:data];
[self.fileHandle writeData:data];// 写入数据到文件
*/
- (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session,
NSURLSessionDataTask *dataTask, NSData *data))block;

@name Setting Download Task Delegate Callbacks【设置下载任务委托回调】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
1.写数据(监听下载进度)
bytesWritten 本次写入的数据大小
totalBytesWritten 下载的数据总大小
totalBytesExpectedToWrite 文件的总大小

// 获得文件的下载进度
NSLog(@"%f",1.0 * totalBytesWritten / totalBytesExpectedToWrite);
*/
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session,
NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten,
int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;

/**
2.当恢复下载的时候调用方法
fileOffset 从什么地方下载
expectedTotalBytes 文件的总大小
*/
- (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session,
NSURLSessionDownloadTask *downloadTask, int64_t fileOffset,
int64_t expectedTotalBytes))block;

/**
3.当下载完成的时候调用
location 文件的临时存储路径

在这个方法里:
拼接文件全路径:stringByAppendingPathComponent:
剪切文件:moveItemAtURL: toURL: error:
*/
- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)
(NSURLSession *session, NSURLSessionDownloadTask *downloadTask,
NSURL *location))block;

接下来可以看到很多常量,这些是通知的 key

@name Notifications【通知】

1
2
3
4
5
6
7
8
9
/**
Posted when a task suspends its execution.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidSuspendNotification;

/**
Posted when a session is invalidated.
*/
FOUNDATION_EXPORT NSString * const AFURLSessionDidInvalidateNotification;

在对外提供的 notification key 里面,使用了 FOUNDATION_EXPORT 来定义全局常量,

全局常量的使用方法:先在.h文件中用 extern 声明此全局常量,然后在.m文件中定义该全局常量。

使用 FOUNDATION_EXPORTextern 或者 define 有什么区别呢?

  • FOUNDATION_EXPORT 在 C文件编译下是和 extern 等同,
  • C++文件编译下是和 extern “C”等同,
  • 在 32位机的环境下又是另外编译情况,在兼容性方面 FOUNDATION_EXPORT 做的会更好。

到这里 .h 文件就学习完了 ~


下面介绍一下实现文件,

先讲几个在 AFN 中的开发技巧

1、为保证线程安全,所有单例都用 dispatch_once 生成,保证只执行一次,代码如下。

1
2
3
4
5
6
7
8
9
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;
}

2、我们经常看到一个 block 要使用 self,会处理成在外部声明一个 weak 变量指向 self,在block 里又声明一个 strong 变量指向 weakSelf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask = task;

self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.uploadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
// 这一段代码主要是设置上传任务的大小,下载任务的大小,上传任务进行时可以取消,
// 可以暂停,上传任务响应恢复处理方法后恢复上传。
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];

if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.downloadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
// 这一段代码主要是设置下载任务进行时可以取消,可以暂停,下载任务响应恢复处理方法后恢复下载。
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
options:NSKeyValueObservingOptionNew
context:NULL];

[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
options:NSKeyValueObservingOptionNew
context:NULL];

[self.downloadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}

weakSelf 是为了 block 不持有self,避免循环引用,而再声明一个 strongSelf 是因为一旦进入block执行,就不允许self在这个执行过程中释放。block 执行完后这个 strongSelf 会自动释放,没有循环引用问题。

3、这个的作用是用来消除特定区域的GCC的编译警告,-Wnonnull则是消除?:警告

1
2
3
4
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop

这个是 clang 的警告 message 列表 Which Clang Warning Is Generating This Message?

4、这里又定义了一个 readwrite 的【NSURLSession *session】作为私有属性。而在 .h 头文件中定义的 session 属性是readonly的。
这么做更安全,在内部我们可对该属性进行读写操作,而暴露给外部时,使用者只能read

1
2
3
4
5
@interface AFURLSessionManager ()
@property (readwrite, nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration;
@property (readwrite, nonatomic, strong) NSOperationQueue *operationQueue;
@property (readwrite, nonatomic, strong) NSURLSession *session;
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier;

mutableTaskDelegatesKeyedByTaskIdentifier 属性是个可变字典。
该属性有大用处,以【taskIdentifier】作为 key,以【task delegate】为 value,
taskdelegate 一对一绑定。

5、这里为什么要使用 inline 呢?

1
2
3
4
5
6
7
8
9
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}

为了防止反汇编之后,在符号表里面看不到你所调用的该方法,否则别人可以通过篡改你的返回值来造成攻击.
iOS安全–使用static inline方式编译函数,防止静态分析,特别是在使用swizzling的时候.

那另一个话题,除了使用swizzling动态替换函数方法之外,还有别的方法么?
参考:轻松学习之 IMP指针的作用

6、在AFURLSessionManager实现里面,是接收到通知的处理,设置代理,增加数据、上传和下载任务的代理以及移除代理,设置block等代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
这里使用到了GCD的信号量控制并发的处理,
首先它创建了一个semaphore,并且设置数量为0,表示只能是一个线程运行,
在 dispatch_semaphore_signal 发送一个信号之后,如果线程仍在运行,
dispatch_semaphore_wait 则会一直等待,直到这个线程结束增加一个信号量,才能继续执行下一个线程.
*/
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}

dispatch_semaphore_signal(semaphore);
}];

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

return tasks;
}

7、

1
2
3
4
5
6
7
8
9
10
11
/**
这个方法里面会去判断 totalBytesExpectedToSend 这个的大小,
我们知道上传请求的时候有三种方法,一个是文件,一个是data,另一个是流,
前两种方法 session 会自动计算 Content-Length,
但是流却不一样,它的大小是未知的,所以在header信息里面一定要提供Content-Length
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend


下面讲一下代码,一层一层进行剖析

1、我们可以看到在外部API调用dataTaskuploadTaskdownloadTask方法实际上都是completionHanlder block返回出来的,但是我们知道网络请求是delegate返回结果的,AFN 内部做了巧妙的操作,他对每个task都增加代理设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
这里面主要有两个步骤:
1.调用NSURLSession的实例方法dataTaskWithRequest:生成dataTask。
url_session_manager_create_task_safely是为了修复某个bug。
2.为该dataTask设置delegate,将dataTask与delegate一一绑定。并且通过block传递出来该请求任务的一些进度、数据、错误信息等。
*/
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
// 1.调用NSURLSession的实例方法dataTaskWithRequest生成dataTask
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});

// 2.为该dataTask设置delegate,将dataTask与delegate一一绑定
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

return dataTask;
}

2、在设置里面,每个task会在内部创建AFURLSessionManagerTaskDelegate对象,并设置completionHandleruploadProgressBlockdownloadProgressBlock回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (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代理类的对象,并为几个属性赋值。
然后调用setDelegate:forTask:将其和dataTask绑定
*/
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;

dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 将delegate和task绑定的核心
[self setDelegate:delegate forTask:dataTask];

delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}

但实现此功能的是setDelegate:forTask:方法,然后delegate对象利用kvotask对一些方法进行监听,并且监听到变化时,通过block返回,将delegate转成block出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
该方法内部有三个步骤:
1.将task的taskIdentifier作为键,将delegate作为值,赋给可变字典mutableTaskDelegatesKeyedByTaskIdentifier。
完成delegate和task的一对一绑定;
2.调用delegate的setupProgressForTask:方法。由task设置delegate对象的uploadProgress和downloadProgress属性的属性值。
3.给该task添加两个观察(启动和暂停)。
*/
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);

[self.lock lock];
// 将task和代理类绑定,task的taskIdentifier作为字典的key,delegate作为字典的value
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 给该task添加两个KVO事件(Resume 和 Suspend)
[self addNotificationObserverForTask:task];
[self.lock unlock];
}

3、 最后,task对接收到的字节数、期望接收到的字节数、发送的字节数、期望发送的字节数设置监听,对上传和下载进程完成的分数进行监听。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
}
}
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}

在这个方法中处理变更通知,这是kvo-键值观察者模式。change中是变更信息,具体是哪些信息取决于注册时的 NSKeyValueObservingOptions

在第一个if判断里面,object判断是否是NSURLSessionTask类或者是否是`NSURLSessionDownloadTask类,然后在if条件下 设置上传和下载的任务的新的大小。当我们进到NSURLSessionDownloadTask的时候,我们可以看到NSURLSessionDownloadTaskNSURLSessionTask`的子类,那为什么还要进行两个类的判断呢?

NSURLSessionTask实际上是Class cluster(类簇),通过NSURLSession生成的task返回的并不一定是指定的task类型。因此kindOfClass并不总会生效,具体可以参见AFURLSessionManager.m在load方法中的说明
特定于当前问题,是由于iOS 7上NSCFURLSessionDownloadTask的基类并不是NSCFURLSessionTask,因此isKindOfClass会出错。查看对应的commit就可以知道了。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AFURLSessionManager 与 AFURLSessionManagerTaskDelegate 的分工

到这里我们可以想一想,既然NSURLSession的delegate是AFURLSessionManager对象,
那么为什么不在AFURLSessionManager中处理所有的事件回调,
搞出来一个AFURLSessionManagerTaskDelegate干什么?

我们知道,NSURLSession的回调有很多,而当我们启动一个task,真正想要获取的信息是什么呢?
就是网络请求最终所返回的数据(我所进行的网络操作成功或是失败,服务器为我返回的数据)呗!
其它的回调,什么认证质询,task需要新的body stream,什么request即将重定向, 统统都是浮云,
都是为了我们能够最终获取到服务器返回的数据所服务的。

另外我们也想要知道我们的网络请求的进度。
总结为两点:
1. 获取最终返回数据
2. 获知当前请求的进度

于是,AFNetWorking 便在纷繁复杂的回调处理中,特意抽象出AFURLSessionManagerTaskDelegate,
用于应付网络返回数据的处理(包括保存中间值,序列化返回值,错误处理)和网络请求进度。
这样做可以使代码结构更分明,逻辑更清晰。

参考资料:
AFNetWorking(3.0)源码分析(二)——AFURLSessionManager
AFNetworking源码阅读2——核心
AFNetworking (3.1.0) 源码解析 <一>

Reading


  • 如果在阅读过程中遇到 error || new ideas,希望你能 issue 我,我会及时补充谢谢。
  • 喜欢可 赞赏 or Star一波;点击左上角关注 或 微众『Codeidea』,在 Demo 更新时收到邮件通知,便捷你的阅读。
既然阅读完了    就在下面留言吧!

标题:iOS 框架详解—「AFURLSessionManager 网络通信核心」

作者:白水

链接:http://githubidea.github.io/SourceAnnotations/AFN3.0+SourceCode-AFURLSessionManager.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和 作者出处。