AFNetworking到底做了什么?(终)

写在开头:
开始正文

首先我们来看看AF2.x的项目目录:

AF2.X源码结构图.png

除了UIKit扩展外,大概就是上述这么多类,其中最重要的有3个类:

1)AFURLConnectionOperation
2)AFHTTPRequestOperation
3)AFHTTPRequestOperationManager

  • 大家都知道,AF2.x是基于NSURLConnection来封装的,而NSURLConnection的创建以及数据请求,就被封装在AFURLConnectionOperation这个类中。所以这个类基本上是AF2.x最底层也是最核心的类。
  • AFHTTPRequestOperation是继承自AFURLConnectionOperation,对它父类一些方法做了些封装。
  • AFHTTPRequestOperationManager则是一个管家,去管理这些这些operation
我们接下来按照网络请求的流程去看看AF2.x的实现:

注:本文会涉及一些NSOperationQueueNSOperation方面的知识,如果对这方面的内容不了解的话,可以先看看雷纯峰的这篇:
iOS 并发编程之 Operation Queues

首先,我们来写一个get或者post请求:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:params
     success:^(AFHTTPRequestOperation *operation, id responseObject) {
         
     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
         
     }];

就这么简单的几行代码,完成了一个网络请求。

接着我们来看看AFHTTPRequestOperationManager的初始化方法:

+ (instancetype)manager {
    return [[self alloc] initWithBaseURL:nil];
}

- (instancetype)init {
    return [self initWithBaseURL:nil];    
}
- (instancetype)initWithBaseURL:(NSURL *)url {
    self = [super init];
    if (!self) {
        return nil;
    }
    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    self.baseURL = url;
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
    //用来调度所有请求的queue
    self.operationQueue = [[NSOperationQueue alloc] init];
    //是否做证书验证
    self.shouldUseCredentialStorage = YES;
    return self;
}

初始化方法很简单,基本和AF3.x类似,除了一下两点:
1)设置了一个operationQueue,这个队列,用来调度里面所有的operation,在AF2.x中,每一个operation就是一个网络请求。
2)设置shouldUseCredentialStorage为YES,这个后面会传给operationoperation会根据这个值,去返回给代理,系统是否做https的证书验证。

然后我们来看看get方法:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
                     parameters:(id)parameters
                        success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                        failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    //拿到request
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"GET" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
    
    AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];

    [self.operationQueue addOperation:operation];
    return operation;
}

方法很简单,如下:
1)用self.requestSerializer生成了一个request,至于如何生成,可以参考之前的文章,这里就不赘述了。
2)生成了一个AFHTTPRequestOperation,然后把这个operation加到我们一开始创建的queue中。

其中创建AFHTTPRequestOperation方法如下:

- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
                                                    success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                    failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    //创建自定义的AFHTTPRequestOperation
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = self.responseSerializer;
    operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage;
    operation.credential = self.credential;
    //设置自定义的安全策略
    operation.securityPolicy = self.securityPolicy;

    [operation setCompletionBlockWithSuccess:success failure:failure];
    operation.completionQueue = self.completionQueue;
    operation.completionGroup = self.completionGroup;
    return operation;
}

方法创建了一个AFHTTPRequestOperation,并把自己的一些参数交给了这个operation处理。

接着往里看:
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    self = [super initWithRequest:urlRequest];
    if (!self) {
        return nil;
    }

    self.responseSerializer = [AFHTTPResponseSerializer serializer];
    return self;
}

除了设置了一个self.responseSerializer,实际上是调用了父类,也是我们最核心的类AFURLConnectionOperation的初始化方法,首先我们要明确这个类是继承自NSOperation的,然后我们接着往下看:

//初始化
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    NSParameterAssert(urlRequest);

    self = [super init];
    if (!self) {
        return nil;
    }

    //设置为ready
    _state = AFOperationReadyState;
    //递归锁
    self.lock = [[NSRecursiveLock alloc] init];
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest;
    
    //是否应该咨询证书存储连接
    self.shouldUseCredentialStorage = YES;

    //https认证策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

    return self;
}

初始化方法中,初始化了一些属性,下面我们来简单的介绍一下这些属性:

  1. _state设置为AFOperationReadyState 准备就绪状态,这是个枚举:
typedef NS_ENUM(NSInteger, AFOperationState) {
    AFOperationPausedState      = -1,  //停止
    AFOperationReadyState       = 1,   //准备就绪
    AFOperationExecutingState   = 2,  //正在进行中
    AFOperationFinishedState    = 3,  //完成
};

这个_state标志着这个网络请求的状态,一共如上4种状态。这些状态其实对应着operation如下的状态:

//映射这个operation的各个状态
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
    switch (state) {
        case AFOperationReadyState:
            return @"isReady";
        case AFOperationExecutingState:
            return @"isExecuting";
        case AFOperationFinishedState:
            return @"isFinished";
        case AFOperationPausedState:
            return @"isPaused";
        default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
            return @"state";
#pragma clang diagnostic pop
        }
    }
}

并且还复写了这些属性的get方法,用来和自定义的state一一对应:

//复写这些方法,与自己的定义的state对应
 - (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}
 - (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}
 - (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}
  1. self.lock这个锁是用来提供给本类一些数据操作的线程安全,至于为什么要用递归锁,是因为有些方法可能会存在递归调用的情况,例如有些需要锁的方法可能会在一个大的操作环中,形成递归。而AF使用了递归锁,避免了这种情况下死锁的发生
  2. 初始化了self.runLoopModes,默认为NSRunLoopCommonModes
  3. 生成了一个默认的 self.securityPolicy,关于这个policy执行的https认证,可以见楼主之前的文章。

这个类为了自定义operation的各种状态,而且更好的掌控它的生命周期,复写了operationstart方法,当这个operation在一个新线程被调度执行的时候,首先就调入这个start方法中,接下来我们它的实现看看:

- (void)start {
    [self.lock lock];
    
    //如果被取消了就调用取消的方法
    if ([self isCancelled]) {
        //在AF常驻线程中去执行
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    //准备好了,才开始
    else if ([self isReady]) {
        //改变状态,开始执行
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    //注意,发起请求和取消请求都是在同一个线程!!包括回调都是在一个线程
    
    [self.lock unlock];
}

这个方法判断了当前的状态,是取消还是准备就绪,然后去调用了各自对应的方法。

  • 注意这些方法都是在另外一个线程中去调用的,我们来看看这个线程:
 + (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        //添加端口,防止runloop直接退出
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
 + (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    
    return _networkRequestThread;
}

这两个方法基本上是被许多人举例用过无数次了...

  • 这是一个单例,用NSThread创建了一个线程,并且为这个线程添加了一个runloop,并且加了一个NSMachPort,来防止runloop直接退出。
  • 这条线程就是AF用来发起网络请求,并且接受网络请求回调的线程,仅仅就这一条线程(到最后我们来讲为什么要这么做)。和我们之前讲的AF3.x发起请求,并且接受请求回调时的处理方式,遥相呼应。

我们接着来看如果准备就绪,start调用的方法:

//改变状态,开始执行
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];

接着在常驻线程中,并且不阻塞的方式,在我们self.runLoopModes的模式下调用:

- (void)operationDidStart {
    [self.lock lock];
    //如果没取消
    if (![self isCancelled]) {
        //设置为startImmediately YES 请求发出,回调会加入到主线程的 Runloop 下,RunloopMode 会默认为 NSDefaultRunLoopMode
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            //把connection和outputStream注册到当前线程runloop中去,只有这样,才能在这个线程中回调
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        //打开输出流
        [self.outputStream open];
        //开启请求
        [self.connection start];
    }
    [self.lock unlock];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
    });
}

这个方法做了以下几件事:

  1. 首先这个方法创建了一个NSURLConnection,设置代理为自己,startImmediately为NO,至于这个参数干什么用的,我们来看看官方文档:

startImmediately
YES if the connection should begin loading data immediately, otherwise NO. If you pass NO, the connection is not scheduled with a run loop. You can then schedule the connection in the run loop and mode of your choice by calling scheduleInRunLoop:forMode: .

大意是,这个值默认为YES,而且任务完成的结果会在主线程的runloop中回调。如果我们设置为NO,则需要调用我们下面看到的:

[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];

去注册一个runloop和mode,它会在我们指定的这个runloop所在的线程中回调结果。

  1. 值得一提的是这里调用了:
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];

这个outputStream在get方法中被初始化了:

 - (NSOutputStream *)outputStream {
    if (!_outputStream) {
        //一个写入到内存中的流,可以通过NSStreamDataWrittenToMemoryStreamKey拿到写入后的数据
        self.outputStream = [NSOutputStream outputStreamToMemory];
    }
    return _outputStream;
}

这里数据请求和拼接并没有用NSMutableData,而是用了outputStream,而且把写入的数据,放到内存中。

  • 其实讲道理来说outputStream的优势在于下载大文件的时候,可以以流的形式,将文件直接保存到本地,这样可以为我们节省很多的内存,调用如下方法设置:
[NSOutputStream outputStreamToFileAtPath:@"filePath" append:YES];
  • 但是这里是把流写入内存中,这样其实这个节省内存的意义已经不存在了。那为什么还要用呢?这里我猜测的是就是为了用它这个可以注册在某一个runloop的指定mode下。 虽然AF使用这个outputStream是肯定在这个常驻线程中的,不会有线程安全的问题。但是要注意它是被声明在.h中的:
@property (nonatomic, strong) NSOutputStream *outputStream;

难保外部不会在其他线程对这个数据做什么操作,所以它相对于NSMutableData作用就体现出来了,就算我们在外部其它线程中去操作它,也不会有线程安全的问题。

  1. 这个connection开始执行了。
  2. 到主线程发送一个任务开始执行的通知。
分割图.png
接下来网络请求开始执行了,就开始触发connection的代理方法了:

代理方法.png

AF2.x一共实现了如上这么多代理方法,这些代理方法,作用大部分和我们之前讲的NSURLSession的代理方法类似,我们只挑几个去讲,如果需要了解其他的方法作用,可以参考楼主之前的文章。

重点讲下面这四个代理:

注意,有一点需要说明,我们之前是把connection注册在我们常驻线程的runloop中了,所以以下所有的代理方法,都是在这仅有的一条常驻线程中回调。

第一个代理
//收到响应,响应头类似相关数据
- (void)connection:(NSURLConnection __unused *)connection
didReceiveResponse:(NSURLResponse *)response
{
    self.response = response;
}

没什么好说的,就是收到响应后,把response赋给自己的属性。

第二个代理
//拼接获取到的数据
- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{
    NSUInteger length = [data length];
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        //如果outputStream 还有空余空间
        if ([self.outputStream hasSpaceAvailable]) {
           
            //创建一个buffer流缓冲区,大小为data的字节数
            const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
           
            //当写的长度小于数据的长度,在循环里
            while (totalNumberOfBytesWritten < (NSInteger)length) {
                //往outputStream写数据,系统的方法,一次就写一部分,得循环写
                numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                //如果 numberOfBytesWritten写入失败了。跳出循环
                if (numberOfBytesWritten == -1) {
                    break;
                }
                //加上每次写的长度
                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        }
        
        //出错
        if (self.outputStream.streamError) {
            //取消connection
            [self.connection cancel];
            //调用失败的方法
            [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            return;
        }
    }

    //主线程回调下载数据大小
    dispatch_async(dispatch_get_main_queue(), ^{
        self.totalBytesRead += (long long)length;

        if (self.downloadProgress) {
            self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
        }
    });
}

这个方法看起来长,其实容易理解而且简单,它只做了3件事:

  1. outputStream拼接数据,具体如果拼接,大家可以读注释自行理解下。
  2. 如果出错则调用:connection:didFailWithError:也就是网络请求失败的代理,我们一会下面就会讲。
  3. 在主线程中回调下载进度。
第三个代理
//完成了调用
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {

    //从outputStream中拿到数据 NSStreamDataWrittenToMemoryStreamKey写入到内存中的流
    self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    //关闭outputStream
    [self.outputStream close];
    
    //如果响应数据已经有了,则outputStream置为nil
    if (self.responseData) {
       self.outputStream = nil;
    }
    //清空connection
    self.connection = nil;
    [self finish];
}
  • 这个代理是任务完成之后调用。我们从outputStream拿到了最后下载数据,然后关闭置空了outputStream。并且清空了connection。调用了finish:
 - (void)finish {
    [self.lock lock];
    //修改状态
    self.state = AFOperationFinishedState;
    [self.lock unlock];

    //发送完成的通知
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
    });
}

把当前任务状态改为已完成,并且到主线程发送任务完成的通知。,这里我们设置状态为已完成。其实调用了我们本类复写的set的方法(前面遗漏了,在这里补充):

 - (void)setState:(AFOperationState)state {
    
    //判断从当前状态到另一个状态是不是合理,在加上现在是否取消。。大神的框架就是屌啊,这判断严谨的。。一层层
    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }
    
    [self.lock lock];
    
    //拿到对应的父类管理当前线程周期的key
    NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
    NSString *newStateKey = AFKeyPathFromOperationState(state);
    
    //发出KVO
    [self willChangeValueForKey:newStateKey];
    [self willChangeValueForKey:oldStateKey];
    _state = state;
    [self didChangeValueForKey:oldStateKey];
    [self didChangeValueForKey:newStateKey];
    [self.lock unlock];
}

这个方法改变state的时候,并且发送了KVO。大家了解NSOperationQueue就知道,如果对应的operation的属性finnished被设置为YES,则代表当前operation结束了,会把operation从队列中移除,并且调用operationcompletionBlock这点很重要,因为我们请求到的数据就是从这个completionBlock中传递回去的(下面接着讲这个完成Block,就能从这里对接上了)。

第四个代理
//请求失败的回调,在cancel connection的时候,自己也主动调用了
- (void)connection:(NSURLConnection __unused *)connection
  didFailWithError:(NSError *)error
{
    //拿到error
    self.error = error;
    //关闭outputStream
    [self.outputStream close];
    //如果响应数据已经有了,则outputStream置为nil
    if (self.responseData) {
        self.outputStream = nil;
    }
    self.connection = nil;
    [self finish];
}

唯一需要说一下的就是这里给self.error赋值,之后完成Block会根据这个error,去判断这次请求是成功还是失败。

至此我们把AFURLConnectionOperation的业务主线讲完了。

分割图.png

我们此时数据请求完了,数据在self.responseData中,接下来我们来看它是怎么回到我们手里的。
我们回到AFURLConnectionOperation子类AFHTTPRequestOperation,有这么一个方法:

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
    self.completionBlock = ^{
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }

        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {
                if (failure) {
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else {
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }

            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

一开始我们在AFHTTPRequestOperationManager中是调用过这个方法的:

[operation setCompletionBlockWithSuccess:success failure:failure];
  • 我们在把成功和失败的Block传给了这个方法。
  • 这个方法也很好理解,就是设置我们之前提到过得completionBlock当自己数据请求完成,就会调用这个Block。然后我们在这个Block中调用传过来的成功或者失败的Block。如果error为空,说明请求成功,把数据传出去,否则为失败,把error信息传出。
  • 这里也类似AF3.x,可以自定义一个完成组和完成队列。数据可以在我们自定义的完成组和队列中回调出去。
  • 除此之外,还有一个有意思的地方:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

之前我们说过,这是在忽略编译器的一些警告。

  • -Wgnu就不说了,是忽略?:。
  • 值得提下的是-Warc-retain-cycles,这里忽略了循环引用的警告。我们仔细看看就知道self持有了completionBlock,而completionBlock内部持有self。这里确实循环引用了。那么AF是如何解决这个循环引用的呢?

我们在回到AFURLConnectionOperation,还有一个方法我们之前没讲到,它复写了setCompletionBlock这个方法。

//复写setCompletionBlock
- (void)setCompletionBlock:(void (^)(void))block {
    [self.lock lock];
    if (!block) {
        [super setCompletionBlock:nil];
    } else {
        __weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //看有没有自定义的完成组,否则用AF的组
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            //看有没有自定义的完成queue,否则用主队列
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
            
            //调用设置的Block,在这个组和队列中
            dispatch_group_async(group, queue, ^{
                block();
            });

            //结束时候置nil,防止循环引用
            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
    [self.lock unlock];
}

注意,它在我们设置的block调用结束的时候,主动的调用:

[strongSelf setCompletionBlock:nil];

把Block置空,这样循环引用不复存在了。

好像我们还遗漏了一个东西,就是返回的数据做类型的解析。其实还真不是楼主故意这样东一块西一块的,AF2.x有些代码确实是这样零散。。当然仅仅是相对3.x来说。AFNetworking整体代码质量,以及架构思想已经强过绝大多数开源项目太多了。。这一点毋庸置疑。

我们来接着看看数据解析在什么地方被调用的把:
- (id)responseObject {
    [self.lock lock];
    if (!_responseObject && [self isFinished] && !self.error) {
        NSError *error = nil;
        //做数据解析
        self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
        if (error) {
            self.responseSerializationError = error;
        }
    }
    [self.lock unlock];
    return _responseObject;
}

AFHTTPRequestOperation 复写了 responseObject 的get方法,
并且把数据按照我们需要的类型(json、xml等等)进行解析。至于如何解析,可以参考楼主之前AF系列的文章,这里就不赘述了。

有些小伙伴可能会说,楼主你是不是把AFSecurityPolicy给忘了啊,其实并没有,它被在 AFURLConnectionOperation中https认证的代理中被调用,我们之前系列的文章已经讲的非常详细了,感兴趣的朋友可以翻到前面的文章去看看。

至此,AF2.x整个业务流程就结束了。

接下来,我们来总结总结AF2.x整个业务请求的流程:


AF2.x请求流程图.png

PS.图片是用page画的,第一次用,画了半个小时有没有...有没有感受到楼主很走心...最近发现写文图太少了,以后会多配图的。来加深大家的理解...

如上图,我们来梳理一下整个流程:
  • 最上层的是AFHTTPRequestOperationManager,我们调用它进行get、post等等各种类型的网络请求
  • 然后它去调用AFURLRequestSerialization做request参数拼装。然后生成了一个AFHTTPRequestOperation实例,并把request交给它。然后把AFHTTPRequestOperation添加到一个NSOperationQueue中。
  • 接着AFHTTPRequestOperation拿到request后,会去调用它的父类AFURLConnectionOperation的初始化方法,并且把相关参数交给它,除此之外,当父类完成数据请求后,它调用了AFURLResponseSerialization把数据解析成我们需要的格式(json、XML等等)。
  • 最后就是我们AF最底层的类AFURLConnectionOperation,它去数据请求,并且如果是https请求,会在请求的相关代理中,调用AFSecurityPolicy做https认证。最后请求到的数据返回。

这就是AF2.x整个做网络请求的业务流程。

我们来解决解决之前遗留下来的问题:为什么AF2.x需要一条常驻线程?

首先如果我们用NSURLConnection,我们为了获取请求结果有以下三种选择:

  1. 在主线程调异步接口
  2. 每一个请求用一个线程,对应一个runloop,然后等待结果回调。
  3. 只用一条线程,一个runloop,所有结果回调在这个线程上。

很显然AF选择的是第3种方式,创建了一条常驻线程专门处理所有请求的回调事件,这个模型跟nodejs有点类似,我们来讨论讨论不选择另外两种方式的原因:

  1. 试想如果我们所有的请求都在主线程中异步调用,好像没什么不可以?那为什么AF不这么做呢...在这里有两点原因(楼主个人总结的,有不同意见,欢迎讨论):
  • 第一是,如果我们放到主线程去做,势必要这么写:
 [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES] 

这样NSURLConnection的回调会被放在主线程中NSDefaultRunLoopMode中,这样我们在其它类似UITrackingRunLoopMode模式下,我们是得不到网络请求的结果的,这显然不是我们想要的,那么我们势必需要调用:

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 

把它加入````NSRunLoopCommonModes```中,试想如果有大量的网络请求,同时回调回来,就会影响我们的UI体验了。

  • 另外一点原因是,如果我们请求数据返回,势必要进行数据解析,解析成我们需要的格式,那么这些解析都在主线程中做,给主线程增加额外的负担。
    又或者我们回调回来开辟一个新的线程去做数据解析,那么我们有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,是不是一笔更大的开销。

所以综述两点原因,我们并不适合在主线程中回调。

  1. 我们一开始就开辟n条线程去做请求,然后设置runloop保活住线程,等待结果回调。
  • 其实看到这,大家想想都觉得这个方法很傻,为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。

综上所述,这就是AF2.x需要一条常驻线程的原因了

至此我们把AF2.x核心流程分析完了。
分割图.png

接着到我们本系列一个最终总结了: AFNetworking到底做了什么?

  • 相信如果从头看到尾的小伙伴,心里都有了一个属于自己的答案。其实在楼主心里,实在不想去总结它,因为AFNetworking中凝聚了太多大牛的思想,根本不是你看完几遍源码所能去议论的。但是想想也知道,如果我说不总结,估计有些看到这的朋友杀人的心都有...
  • 所以我还是赶鸭子上架,来总结总结它。
AFNetworking的作用总结:

一. 首先我们需要明确一点的是:
相对于AFNetworking2.x,AFNetworking3.x确实没那么有用了。AFNetworking之前的核心作用就是为了帮我们去调度所有的请求。但是最核心地方却被苹果的NSURLSession给借鉴过去了,嗯...是借鉴。这些请求的调度,现在完全由NSURLSession给做了,AFNetworking3.x的作用被大大的削弱了。
二. 但是除此之外,其实它还是很有用的:

  1. 首先它帮我们做了各种请求方式request的拼接。想想如果我们用NSURLSession,我们去做请求,是不是还得自己去考虑各种请求方式下,拼接参数的问题。
  • 它还帮我们做了一些公用参数(session级别的),和一些私用参数(task级别的)的分离。它用Block的形式,支持我们自定义一些代理方法,如果没有实现的话,AF还帮我们做了一些默认的处理。而如果我们用NSURLSession的话,还得参照AF这么一套代理转发的架构模式去封装。

  • 它帮我们做了自定义的https认证处理。看过楼主之前那篇AFNetworking之于https认证的朋友就知道,如果我们自己用NSURLSession实现那几种自定义认证,需要多写多少代码...

  • 对于请求到的数据,AF帮我们做了各种格式的数据解析,并且支持我们设置自定义的code范围,自定义的数据方式。如果不在这些范围中,则直接调用失败block。如果用NSURLSession呢?这些都自己去写吧...(你要是做过各种除json外其他的数据解析,就会知道这里面坑有多少...)

  • 对于成功和失败的回调处理。AF帮我们在数据请求到,到回调给用户之间,做了各种错误的判断,保证了成功和失败的回调,界限清晰。在这过程中,AF帮我们做了太多的容错处理,而NSURLSession呢?只给了一个完成的回调,我们得多做多少判断,才能拿到一个确定能正常显示的数据?

  • ......

  • ...

光是这些网络请求的业务逻辑,AF帮我们做的就太多太多,当然还远不仅于此。它用凝聚着许多大牛的经验方式,帮我在有些处理中做了最优的选择,比如我们之前说到的,回调线程数设置为1的问题...帮我们绕开了很多的坑,比如系统内部并行创建task导致id不唯一等等...

三. 而如果我们需要一些UIKit的扩展,AF则提供了最稳定,而且最优化实现方式:

  • 就比如之前说到过得那个状态栏小菊花,如果是我们自己去做,得多写多少代码,而且实现的还没有AF那样质量高。
  • 又或者AFImageDownloader,它对于组图片之间的下载协调,以及缓存使用的之间线程调度。对于线程,锁,以及性能各方面权衡,找出最优化的处理方式,试问小伙伴们自己基于NSURLSession去写,能到做几分...

所以最后的结论是:AFNetworking虽然变弱了,但是它还是很有用的。用它真的不仅仅是习惯,而是因为它确实帮我们做了太多。

写在最后:
  • 这个系列终于结束了,从想要开始写这个系列,到真正结束,花了大半个月的时间。其实3.x源码早在刚出来的时候就读过了,为了写它,又拿出来重新读了一遍。而且为了让大家更容易理解,楼主在大部分代码,几乎是一行一个注释的在标注,在这里浪费了大量的时间。
    然而看到大家的赞和评论,还有有些素昧平生的朋友的打赏。让我发自内心的开心。这一切都太值得...
    如果你能看到这里,除了感谢还是感谢以后还会分享更多好的文章,谢谢
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容