[无限互联]AFNetworking核心类AFURLConnectionOperation的详解

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

上一篇我们了解了AFNetworking各个模块的功能,今天我们来了解一下AFNetworking源码的实现
我们先看看AFURLConnectionOperation作为AFNetworking最核心的类到底有什么神奇之处!
1.线程
先来看看NSURLConnection 发送请求时的线程情况,NSURLConnection 是被设计成异步发送的,调用了start方法后,NSURLConnection 会新建一些线程用底层的CFSocket 去发送和接收请求,在发送和接收的一些事件发生后通知原来线程的Runloop 去回调事件。

NSURLConnection 的同步方法sendSynchronousRequest 方法也是基于异步的,同样要在其他线程去处理请求的发送和接收,只是同步方法会手动block住线程,发送状态的通知也不是通过RunLoop 进行。

NSURLConnection发送有以下三种方式
∙在主线程调异步接口
∙在子线程调同步接口
∙在子线程调异步接口
AFNetworking使用的是最后一种方式方法,AFNetworking内部相关线程大致的关系如下图所示
NSURLConnection是一个系统控件,所以我们可以把NSURLConnection当做一个黑盒,只管它的 start 和 callback 就行了。

如果使用 AFHttpRequestOperationManager 的接口发送请求,这些请求会统一在一个 NSOperationQueue 里去发,所以多了上面NSOperationQueue 的一个线程。

相关代码:
[objc]view plaincopy
1.//-------------------------线程--------------------
2./*
3.子线程调用异步接口,子线程需要有 Runloop 去接收异步回调事件,这里也可以每个请求
都新建一条
4.带有 Runloop 的线程去侦听回调,但这一点好处都没有,既然是异步回调,除了处理回调
内容,其他
5.时间线程都是空闲可利用的,所有请求共用一个响应的线程就够了。

6. */
7.//获取当前的NSRunLoop,子线程使用共同的Runloop
8.+ (void)networkRequestThreadEntryPoint:(id)__unused object {
9.@autoreleasepool {
10. [[NSThread currentThread] setName:@"AFNetworking"];
11.NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
12. [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
13. [runLoop run];
14. }
15.}
16.//创建新的子线程
17.+ (NSThread *)networkRequestThread {
18. <span style="white-space:pre"> </span> static NSThread *_networkRequ
estThread = nil;
19. <span style="white-space:pre"> </span> static dispatch_once_t oncePr
edicate;
20. <span style="white-space:pre"> </span> dispatch_once(&oncePredicate,
^{
21. <span style="white-space:pre"> </span> _networkRequestThread = [[NST
hread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint: ) object:nil];
22. <span style="white-space:pre"> </span> [_networkRequestThread start];
23. });
24.return _networkRequestThread;
25.}
26.//初始化NSURLConnection对象
27.- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
28. NSParameterAssert(urlRequest);
29.self = [super init];
30.if (!self) {
31.return nil;
32. }
33. _state = AFOperationReadyState;
34.self.lock = [[NSRecursiveLock alloc] init];
= kAFNetworkingLockName;
36.self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
37.self.request = urlRequest;
38.self.shouldUseCredentialStorage = YES;
39.self.securityPolicy = [AFSecurityPolicy defaultPolicy];
40.return self;
41.}
[objc]view plaincopy
1.//------------------------线程--------------------
2.//线程开始
3.- (void)start {
4.//加锁,保护线程
5. [self.lock lock];
6.if ([self isCancelled]) { //取消线程
7. [self performSelector:@selector(cancelConnection) onThread:[[self cl
ass] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
8. } else if ([self isReady]) { //线程已准备
9.self.state = AFOperationExecutingState; //将线程调为执行状态
10.
11. [self performSelector:@selector(operationDidStart) onThread:[[self c
lass] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopMode s allObjects]];
12. }
13.//线程执行完毕,解锁
14. [self.lock unlock];
15.}
16.//线程已开始
17.- (void)operationDidStart {
18. [self.lock lock];
19.if (![self isCancelled]) {
20.//创建链接对象
21.self.connection = [[NSURLConnection alloc] initWithRequest:self.requ
est delegate:self startImmediately:NO];
22.//获取当前的NSRunLoop,用来接收异步回调事件
23.NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
24.for (NSString *runLoopMode in self.runLoopModes) {
25.//执行线程
26. [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
27. [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode
];
28. }
29.//开始链接
30. [self.connection start];
31. }
32. [self.lock unlock];
33.//回到主线程,发送线程开始通知
34. dispatch_async(dispatch_get_main_queue(), ^{
35. [[NSNotificationCenter defaultCenter] postNotificationName:AFNetwork
ingOperationDidStartNotification object:self];
36. });
37.}
38.//线程完成
39.- (void)finish {
40. [self.lock lock];
41.self.state = AFOperationFinishedState;
42. [self.lock unlock];
43.//发送通知,链接完成
44. dispatch_async(dispatch_get_main_queue(), ^{
45. [[NSNotificationCenter defaultCenter] postNotificationName:AFNetwork
ingOperationDidFinishNotification object:self];
46. });
47.}
48.//线程取消
49.- (void)cancel {
50. [self.lock lock];
51.if (![self isFinished] && ![self isCancelled]) {
52. [super cancel];
53.
54.if ([self isExecuting]) {
55. [self performSelector:@selector(cancelConnection) onThread:[[sel
f class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopM
odes allObjects]];
56. }
57. }
58. [self.lock unlock];
59.}
60.//取消链接
61.- (void)cancelConnection {
62.NSDictionary *userInfo = nil;
63.if ([self.request URL]) {
64. userInfo = [NSDictionary dictionaryWithObject:[self.request URL] for
Key:NSURLErrorFailingURLErrorKey];
65. }
66.NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErr
orCancelled userInfo:userInfo];
67.if (![self isFinished]) { //处于链接状态,取消链接
68.if (self.connection) {
69.//取消链接
70. [self.connection cancel];
71. [self performSelector:@selector(connection:didFailWithError:) wi
thObject:self.connection withObject:error];
72. } else { //链接完成,则直接结束
73.// Accomodate race condition where `self.connection` has not yet
been set before cancellation
74.self.error = error;
75. [self finish];
76. }
77. }
78.}
2.状态机
继承NSOperation 有个很麻烦的东西要处理,就是改变状态时需要发KVO 通知,否则这个类加入NSOperationQueue 不可用了。

NSOperationQueue 是用KVO 方式侦听NSOperation 状态的改变,以判断这个任务当前是否已完成,完成的任务需要在队列中除去并释放。

AFURLConnectionOperation 对此做了个状态机,统一搞定状态切换以及发 KVO 通知的问
题,内部要改变状态时,就只需要类似 self.state = AFOperationReadyState 的调用而不需要做其他了,状态改变的 KVO 通知在 setState 里发出。

总的来说状态管理相关代码就三部分,一是限制一个状态可以切换到其他哪些状态,避免状态切换混乱,二是状态 Enum值与 NSOperation 四个状态方法的对应,三是在 setState
时统一发 KVO 通知。

相关代码如下:
[objc]view plaincopy
1.//-------------------------状态机
2.//该方法的作用:状态 Enum值与 NSOperation 四个状态方法的对应
3.static inline NSString * AFKeyPathFromOperationState(AFOperationState state)
{
4.switch (state) {
5.case AFOperationReadyState:
6.return@"isReady";
7.case AFOperationExecutingState:
8.return@"isExecuting";
9.case AFOperationFinishedState:
10.return@"isFinished";
11.case AFOperationPausedState:
12.return@"isPaused";
13. default: {
14.#pragma clang diagnostic push
15.#pragma clang diagnostic ignored "-Wunreachable-code"
16.return@"state";
17.#pragma clang diagnostic pop
18. }
19. }
20.}
21.//NSOperation 状态的切换:限制一个状态可以切换到其他哪些状态,避免状态切换混乱
22.static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOp
erationState toState, BOOL isCancelled) {
23.switch (fromState) {
24.case AFOperationReadyState:
25.switch (toState) {
26.case AFOperationPausedState:
27.case AFOperationExecutingState:
28.return YES;
29.case AFOperationFinishedState:
30.return isCancelled;
31. default:
32.return NO;
33. }
34.case AFOperationExecutingState:
35.switch (toState) {
36.case AFOperationPausedState:
37.case AFOperationFinishedState:
38.return YES;
39. default:
40.return NO;
41. }
42.case AFOperationFinishedState:
43.return NO;
44.case AFOperationPausedState:
45.return toState == AFOperationReadyState;
46. default: {
47.#pragma clang diagnostic push
48.#pragma clang diagnostic ignored "-Wunreachable-code"
49.switch (toState) {
50.case AFOperationPausedState:
51.case AFOperationReadyState:
52.case AFOperationExecutingState:
53.case AFOperationFinishedState:
54.return YES;
55. default:
56.return NO;
57. }
58. }
59.#pragma clang diagnostic pop
60. }
61.}
[objc]view plaincopy
1.//-------------------------状态机
2.//NSOperationQueue 是用KVO方式侦听 NSOperation 状态的改变
3.//在该方法里统一发 KVO 通知给 NSOperationQueue,以判断这个任务当前是否已完成,完成
的任务需要在队列中除去并释放。

4.- (void)setState:(AFOperationState)state {
5.if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
6.return;
7. }
8. [self.lock lock];
9.NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
10.NSString *newStateKey = AFKeyPathFromOperationState(state);
11. [self willChangeValueForKey:newStateKey];
12. [self willChangeValueForKey:oldStateKey];
13. _state = state;
14. [self didChangeValueForKey:oldStateKey];
15. [self didChangeValueForKey:newStateKey];
16. [self.lock unlock];
17.}
[objc]view plaincopy
1.还有其他相关代码:
-setState:, -isPaused:, -isReady:, -isExecuting:, -isFinished:.
3.NSURLConnectionDelegate
处理NSURLConnection Delegate 的内容不多,代码也是按请求回调的顺序排列下去,十分易读,主要流程就是接收到响应的时候打开outputStream,接着有数据过来就往outputStream 写,在上传/接收数据过程中会回调上层传进来的相应的callback,在请求完成回调到connectionDidFinishLoading 时,关闭outputStream,用outputStream 组装responseData 作为接收到的数据,把NSOperation 状态设为finished,表示任务完成,NSOperation 会自动调用completeBlock,再回调到上层。

相关代码如下:
[objc]view plaincopy
1.//使用NSURLConnection时,用该方法检查证书的有效性
2.- (void)connection:(NSURLConnection *)connection
3.willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)ch
allenge{}
[objc]view plaincopy
1./*
2. connection: willSendRequest: redirectResponse:
3.这个方法在请求将要被发送出去之前会调用
4.返回值是一个NSURLRequest,就是那个真正将要被发送的请求
5.第二个参数request就是被重定向处理过后的请求
6.第三个参数
<span style="font-family: Arial, Helvetica, sans-serif;">redirectResponse</span><sp an style="font-family: Arial, Helvetica, sans-serif;">是触发重定向请求的响应包.默认是支持跳转的。

</span>
7. */
8.- (NSURLRequest *)connection:(NSURLConnection *)connection
9. willSendRequest:(NSURLRequest *)request
10. redirectResponse:(NSURLResponse *)redirectResponse
11.{
12.if (self.redirectResponse) {
13.return self.redirectResponse(connection, request, redirectResponse);
14. } else {
15.return request;
16. }
17.}
18.//上传数据
19.- (void)connection:(NSURLConnection __unused *)connection
20. didSendBodyData:(NSInteger)bytesWritten
21. totalBytesWritten:(NSInteger)totalBytesWritten
22.totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
23.{
24.//上传数据过程中回调上层传进来的相应的callback
25.if (self.uploadProgress) {
26. dispatch_async(dispatch_get_main_queue(), ^{
27.self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten,
totalBytesExpectedToWrite);
28. });
29. }
30.}
31.//代理对象接收到响应的时候打开 outputStream
32.- (void)connection:(NSURLConnection __unused *)connection
33.didReceiveResponse:(NSURLResponse *)response
34.{
35.self.response = response;
36. [self.outputStream open];
37.}
38.//有数据过来时,往outputStream写数据
39.- (void)connection:(NSURLConnection __unused *)connection
40. didReceiveData:(NSData *)data
41.{
42. NSUInteger length = [data length];
43.while (YES) {
44. NSInteger totalNumberOfBytesWritten = 0;
45.if ([self.outputStream hasSpaceAvailable]) {
46.const uint8_t *dataBuffer = (uint8_t *)[data bytes];
47. NSInteger numberOfBytesWritten = 0;
48.while (totalNumberOfBytesWritten < (NSInteger)length) {
49. numberOfBytesWritten = [self.outputStream write:&dataBuffer[
(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberO fBytesWritten)];
50.if (numberOfBytesWritten == -1) {
51.break;
52. }
53. totalNumberOfBytesWritten += numberOfBytesWritten;
54. }
55.break;
56. }
57.if (self.outputStream.streamError) {
58. [self.connection cancel];
59. [self performSelector:@selector(connection:didFailWithError:) wi
thObject:self.connection withObject:self.outputStream.streamError];
60.return;
61. }
62. }
63.//接收数据过程中会回调上层传进来的相应的callback
64. dispatch_async(dispatch_get_main_queue(), ^{
65.self.totalBytesRead += (long long)length;
66.
67.if (self.downloadProgress) {
68.self.downloadProgress(length, self.totalBytesRead, self.response
.expectedContentLength);
69. }
70. });
71.}
72.//请求完成回调到 connectionDidFinishLoading 时,关闭 outputStream
73.//用 outputStream 组装 responseData 作为接收到的数据,把 NSOperation 状态设
为 finished,表示任务完成
74.- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
75.self.responseData = [self.outputStream propertyForKey:NSStreamDataWritte
nToMemoryStreamKey];
76. [self.outputStream close];
77.if (self.responseData) {
78.self.outputStream = nil;
79. }
80.self.connection = nil;
81. [self finish];
82.}
4.setCompleteBlock
AFNetworking重写NSOperation提供的setCompletionBlock,用于任务完成时回调传进来的block,并且实现消除循环引用。

在 NSOperation 的实现里,completionBlock 是 NSOperation 对象的一个成员,
NSOperation 对象持有着 completionBlock,若传进来的 block 用到了 NSOperation 对象,或者 block 用到的对象持有了这个 NSOperation 对象,就会造成循环引用。

这里执行
完 block 后调用 [strongSelf setCompletionBlock:nil] 把 completionBlock 设成
nil,手动释放 self(NSOperation对象) 持有的 completionBlock 对象,打破循环引用。

相关代码如下:
[objc]view plaincopy
1.<span style="font-size:14px;">//任务完成,回调block
2.- (void)setCompletionBlock:(void (^)(void))block {
3. [self.lock lock];
4.if (!block) {
5. [super setCompletionBlock:nil];
6. } else {
7.//weakSelf是为了block不持有self,避免循环引用
8. __weak __typeof(self)weakSelf = self;
9. [super setCompletionBlock:^ {
10.//再声明一个strongSelf是因为一旦进入block执行,就不允许self在这个
执行过程中释放。

block执行完后这个strongSelf会自动释放,没有循环引用问题。

11. __strong __typeof(weakSelf)strongSelf = weakSelf;
12.#pragma clang diagnostic push
13.#pragma clang diagnostic ignored "-Wgnu"
14. dispatch_group_t group = pletionGroup ?: url_reque
st_operation_completion_group();
15. dispatch_queue_t queue = pletionQueue ?: dispatch_
get_main_queue();
16.#pragma clang diagnostic pop
17. dispatch_group_async(group, queue, ^{
18.//传入一个 block 作为任务执行完成时(state状态机变为finished时)
的回调
19. block();
20. });
21./*
22.循环引用:NSOperation持有completionBlock,若传进来的block 用到
了 NSOperation 对象,
23.或者 block 用到的对象持有了这个 NSOperation 对象,就会造成循环引用
24.解决方法:
25.消除循环引用,手动释放 self(NSOperation对象) 持有
的 completionBlock 对象,打破循环引用
26. */
27. dispatch_group_notify(group, url_request_operation_completion_qu
eue(), ^{
28. [strongSelf setCompletionBlock:nil];
29. });
30. }];
31. }
32. [self.lock unlock];
33.}</span>
5.batchOfRequestOperations
这里额外提供了一个便捷接口,可以传入一组请求,在所有请求完成后回调
complionBlock,在每一个请求完成时回调progressBlock 通知外面有多少个请求已完成[objc]view plaincopy
1.<span style="font-size:14px;">+ (NSArray *)batchOfRequestOperations:(NSArray
*)operations
2. progressBlock:(void (^)(NSUInteger numberOfFinishedO
perations, NSUInteger totalNumberOfOperations))progressBlock
3. completionBlock:(void (^)(NSArray *operations))complet
ionBlock
4.{
5.//请求不存在,或者请求数为0,返回
6.if (!operations || [operations count] == 0) {
7.return @[[NSBlockOperation blockOperationWithBlock:^{
8. dispatch_async(dispatch_get_main_queue(), ^{
9.if (completionBlock) {
10. completionBlock(@[]);
11. }
12. });
13. }]];
14. }
15. __block dispatch_group_t group = dispatch_group_create();
16.//任务数为0时执行dispatch_group_notify的内容
17.NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWit
hBlock:^{
18. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
19.if (completionBlock) {
20. completionBlock(operations);
21. }
22. });
23. }];
24.//取出每一个请求(任务)
25.for (AFURLConnectionOperation *operation in operations) {
26. pletionGroup = group;
27.void (^originalCompletionBlock)(void) = [pletionBlock c
opy];
28. __weak __typeof(operation)weakOperation = operation;
29. pletionBlock = ^{
30. __strong __typeof(weakOperation)strongOperation = weakOperation;
31.#pragma clang diagnostic push
32.#pragma clang diagnostic ignored "-Wgnu"
33. dispatch_queue_t queue = pletionQueue ?: disp
atch_get_main_queue();
34.#pragma clang diagnostic pop
35.//异步执行任务
36. dispatch_group_async(group, queue, ^{
37.if (originalCompletionBlock) {
38.//任务完成后回调block
39. originalCompletionBlock();
40. }
41. NSUInteger numberOfFinishedOperations = [[operations indexes
OfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx, BOOL__unused *stop) {
42.return [op isFinished];
43. }] count];
44.
45.//在每一个请求完成时回调 progressBlock 通知外面有多少个请求已完
成。

46.if (progressBlock) {
47. progressBlock(numberOfFinishedOperations, [operations co
unt]);
48. }
49.
50. dispatch_group_leave(group); //类似release,任务数-1
51. });
52. };
53. dispatch_group_enter(group); //类似retain,任务数+1
54. [batchedOperation addDependency:operation];
55. }
56.return [operations arrayByAddingObject:batchedOperation];
57.}</span>
6.锁、序列化、backgroundTask
锁:AFURLConnectionOperation 有一把递归锁,在所有会访问/修改成员变量的对外接口都加了锁,因为这些对外的接口用户是可以在任意线程调用的,对于访问和修改成员变量的接口,必须用锁保证线程安全。

序列化:AFNetworking 的多数类都支持序列化,实现的是NSSecureCoding 的接口,用-decodeObjectOfClass:forKey: 方法,指定Class保证序列化后的数据不被篡改,若不指定Class,-decode 出来的对象可能不是原来的对象,有潜在风险。

backgroundTask:这里提供了setShouldExecuteAsBackgroundTaskWithExpirationHandler 接口,决定APP进入后台后是否继续发送接收请求,并在后台执行时间超时后取消所有请求。

在dealloc 里需要调用[application endBackgroundTask:] ,告诉系统这个后台任务已经完成,不然系统会一直让你的APP运行在后台,直到超时。

7.AFHTTPRequestOperation
AFHTTPRequestOperation 继承了AFURLConnectionOperation,实现的功能比较少,主要多了responseSerializer,暂停下载断点续传,以及提供接口请求成功失败的回调接口。

理解了AFURLConnectionOperation,就会觉得AFHTTPRequestOperation比较简单,所以具体的就不写了。

-
(void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operat ion,idresponseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation,NS Error *error))failure;
8.AFHTTPRequestOperationManager AFHTTPRequestOperationManager封装了AFNetworking其他功能的各个模块,如AFHTTPRequestSerializer(请求序列化),AFHTTPResponseSerializer(响应序列化),AFSecurityPolicy(安全策略),AFNetworkReachabilityManager(可达性),封装了HTTP 请求所相关的代码
并且将所有的请求添加到同一个NSOperationQueue请求队列里。

并且封装HTTP 请求的常见方式,GET / POST / PUT / DELETE / PATCH……
NSURLConnection相关的代码就解析到这里。

下一篇讲AFNetworking其他功能模块!。

相关文档
最新文档