iOS开发技巧暂时没看却需要看的iOS开发部落

AFNetworking到底做了什么?(二)

2016-12-05  本文已影响9609人  涂耀辉
接着上一篇的内容往下讲,如果没看过上一篇内容可以点这:

AFNetworking到底做了什么?

之前我们讲到NSUrlSession代理这一块:

代理8:
/*
 task完成之后的回调,成功和失败都会回调这里
 函数讨论:
 注意这里的error不会报告服务期端的error,他表示的是客户端这边的eroor,比如无法解析hostname或者连不上host主机。
 */
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{   
    //根据task去取我们一开始创建绑定的delegate
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background
    if (delegate) {
        //把代理转发给我们绑定的delegate
        [delegate URLSession:session task:task didCompleteWithError:error];
        //转发完移除delegate
        [self removeDelegateForTask:task];
    }
   
    //自定义Block回调
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }  
}

这个代理就是task完成了的回调,方法内做了下面这几件事:

 - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}
 - (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);
    //移除跟AF代理相关的东西
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    [self.lock lock];
    [delegate cleanUpProgressForTask:task];
    [self removeNotificationObserverForTask:task];
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}
NSURLSessionDataDelegate:
代理9:
//收到服务器响应后调用
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    //设置默认为继续进行
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;

    //自定义去设置
    if (self.dataTaskDidReceiveResponse) {
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }

    if (completionHandler) {
        completionHandler(disposition);
    }
}

官方文档翻译如下:

函数作用:
告诉代理,该data task获取到了服务器端传回的最初始回复(response)。注意其中的completionHandler这个block,通过传入一个类型为NSURLSessionResponseDisposition的变量来决定该传输任务接下来该做什么:
NSURLSessionResponseAllow 该task正常进行
NSURLSessionResponseCancel 该task会被取消
NSURLSessionResponseBecomeDownload 会调用URLSession:dataTask:didBecomeDownloadTask:方法来新建一个download task以代替当前的data task
NSURLSessionResponseBecomeStream 转成一个StreamTask

函数讨论:
该方法是可选的,除非你必须支持“multipart/x-mixed-replace”类型的content-type。因为如果你的request中包含了这种类型的content-type,服务器会将数据分片传回来,而且每次传回来的数据会覆盖之前的数据。每次返回新的数据时,session都会调用该函数,你应该在这个函数中合理地处理先前的数据,否则会被新数据覆盖。如果你没有提供该方法的实现,那么session将会继续任务,也就是说会覆盖之前的数据。

总结一下:

代理10:
//上面的代理如果设置为NSURLSessionResponseBecomeDownload,则会调用这个方法
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    //因为转变了task,所以要对task做一个重新绑定
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        [self removeDelegateForTask:dataTask];
        [self setDelegate:delegate forTask:downloadTask];
    }
    //执行自定义Block
    if (self.dataTaskDidBecomeDownloadTask) {
        self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
    }
}

按照顺序来,其实还有个AF没有去实现的代理:

//AF没实现的代理
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;

这个也是之前的那个代理,设置为NSURLSessionResponseBecomeStream则会调用到这个代理里来。会新生成一个NSURLSessionStreamTask来替换掉之前的dataTask。

代理11:
//当我们获取到数据就会调用,会被反复调用,请求到的数据就在这被拼装完整
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}
代理12:
/*当task接收到所有期望的数据后,session会调用此代理方法。
*/
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
    NSCachedURLResponse *cachedResponse = proposedResponse;

    if (self.dataTaskWillCacheResponse) {
        cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
    }
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

官方文档翻译如下:

函数作用:
询问data task或上传任务(upload task)是否缓存response。

函数讨论:
当task接收到所有期望的数据后,session会调用此代理方法。如果你没有实现该方法,那么就会使用创建session时使用的configuration对象决定缓存策略。这个代理方法最初的目的是为了阻止缓存特定的URLs或者修改NSCacheURLResponse对象相关的userInfo字典。
该方法只会当request决定缓存response时候调用。作为准则,responses只会当以下条件都成立的时候返回缓存:
该request是HTTP或HTTPS URL的请求(或者你自定义的网络协议,并且确保该协议支持缓存)
确保request请求是成功的(返回的status code为200-299)
返回的response是来自服务器端的,而非缓存中本身就有的
提供的NSURLRequest对象的缓存策略要允许进行缓存
服务器返回的response中与缓存相关的header要允许缓存
该response的大小不能比提供的缓存空间大太多(比如你提供了一个磁盘缓存,那么response大小一定不能比磁盘缓存空间还要大5%)

NSURLSessionDownloadDelegate
代理13:
//下载完成的时候调用

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    //这个是session的,也就是全局的,后面的个人代理也会做同样的这件事
    if (self.downloadTaskDidFinishDownloading) {
        
        //调用自定义的block拿到文件存储的地址
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            //从临时的下载路径移动至我们需要的路径
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            //如果移动出错
            if (error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }
            return;
        }
    }
    //转发代理
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}
 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidCompleteWithError:(NSError *)error;
 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

总共就这3个方法,被转调到AF自定义delegate中。

代理14:
//周期性地通知下载进度调用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

简单说一下这几个参数:
bytesWritten 表示自上次调用该方法后,接收到的数据字节数
totalBytesWritten表示目前已经接收到的数据字节数
totalBytesExpectedToWrite 表示期望收到的文件总字节数,是由Content-Length header提供。如果没有提供,默认是NSURLSessionTransferSizeUnknown。

代理15:
//当下载被取消或者失败后重新恢复下载时调用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    //交给自定义的Block去调用
    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

官方文档翻译:

函数作用:
告诉代理,下载任务重新开始下载了。

函数讨论:
如果一个正在下载任务被取消或者失败了,你可以请求一个resumeData对象(比如在userInfo字典中通过NSURLSessionDownloadTaskResumeData这个键来获取到resumeData)并使用它来提供足够的信息以重新开始下载任务。
随后,你可以使用resumeData作为downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的参数。当你调用这些方法时,你将开始一个新的下载任务。一旦你继续下载任务,session会调用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask参数表示的就是新的下载任务,这也意味着下载重新开始了。

总结一下:

至此NSUrlSesssion的delegate讲完了。大概总结下:

分割图.png

接下来我们来看转发到AF的deleagate,一共3个方法:

AF代理1:
//AF实现的代理!被从urlsession那转发到这

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
 
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    
    //1)强引用self.manager,防止被提前释放;因为self.manager声明为weak,类似Block

    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    //用来存储一些相关信息,来发送通知用的
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    //存储responseSerializer响应解析对象
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    
    //注意这行代码的用法,感觉写的很Nice...把请求到的数据data传出去,然后就不要这个值了释放内存
    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;
    }

    //继续给userinfo填数据
    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }
    //错误处理
    if (error) {
        
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
        
        //可以自己自定义完成组 和自定义完成queue,完成回调
        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 {
        //url_session_manager_processing_queue AF的并行队列
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            
            //解析数据
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
            
            //如果是下载文件,那么responseObject为下载的路径
            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            //写入userInfo
            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }
            
            //如果解析错误
            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }
            //回调结果
            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];
                });
            });
        });
    }
#pragma clang diagnostic pop
}

这个方法是NSUrlSession任务完成的代理方法中,主动调用过来的。配合注释,应该代码很容易读,这个方法大概做了以下几件事:

  1. 生成了一个存储这个task相关信息的字典:userInfo,这个字典是用来作为发送任务完成的通知的参数。
static dispatch_queue_t url_session_manager_processing_queue() {
    static dispatch_queue_t af_url_session_manager_processing_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
    });

    return af_url_session_manager_processing_queue;
}

注意AF的优化的点,虽然代理回调是串行的(不明白可以见本文最后)。但是数据解析这种费时操作,确是用并行线程来做的。

responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

我们点进去看看:

  @protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

  - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end

原来就是这么一个协议方法,各种类型的responseSerializer类,都是遵守这个协议方法,实现了一个把我们请求到的data转换为我们需要的类型的数据的方法。至于各种类型的responseSerializer如何解析数据,我们到代理讲完再来补充。

static dispatch_group_t url_session_manager_completion_group() {
    static dispatch_group_t af_url_session_manager_completion_group;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_completion_group = dispatch_group_create();
    });

    return af_url_session_manager_completion_group;
}

和主队列回调。AF没有用这个GCD组做任何处理,只是提供这个接口,让我们有需求的自行调用处理。如果有对多个任务完成度的监听,可以自行处理。
而队列的话,如果你不需要回调主线程,可以自己设置一个回调队列。

dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });

这个通知这回AF有用到了,在我们对UIKit的扩展中,用到了这个通知。

AF代理2:
- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //拼接数据
    [self.mutableData appendData:data];
}

同样被NSUrlSession代理转发到这里,拼接了需要回调的数据。

AF代理3:
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    //AF代理的自定义Block
    if (self.downloadTaskDidFinishDownloading) {
        //得到自定义下载路径
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        
        if (self.downloadFileURL) {
            //把下载路径移动到我们自定义的下载路径
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
            
            //错误发通知
            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

下载成功了被NSUrlSession代理转发到这里,这里有个地方需要注意下:

 [manager downloadTaskWithRequest:resquest progress:nil destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
    return path;
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
}];

这个地方return的path就是对应的这个代理方法里的path,我们调用最终会走到这么一个方法:

  - (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] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    //返回地址的Block
    if (destination) {
        
        //有点绕,就是把一个block赋值给我们代理的downloadTaskDidFinishDownloading,这个Block里的内部返回也是调用Block去获取到的,这里面的参数都是AF代理传过去的。
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            //把Block返回的地址返回
            return destination(location, task.response);
        };
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];

    delegate.downloadProgressBlock = downloadProgressBlock;
}

清楚的可以看到地址被赋值给AF的Block了。

至此AF的代理也讲完了,数据或错误信息随着AF代理成功失败回调,回到了用户的手中。

分割图.png

接下来我们来补充之前AFURLResponseSerialization这一块是如何解析数据的:

AFURLResponseSerialization.png

如图所示,AF用来解析数据的一共上述这些方法。第一个实际是一个协议方法,协议方法如下:

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error;

@end

而后面6个类都是遵守这个协议方法,去做数据解析。这地方可以再次感受一下AF的设计模式...接下来我们就来主要看看这些类对这个协议方法的实现:

AFHTTPResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
    return data;
}
// 判断是不是可接受类型和可接受code,不是则填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    //response是否合法标识
    BOOL responseIsValid = YES;
    //验证的error
    NSError *validationError = nil;

    //如果存在且是NSHTTPURLResponse
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        
        //主要判断自己能接受的数据类型和response的数据类型是否匹配,
        //如果有接受数据类型,如果不匹配response,而且响应类型不为空,数据长度不为0
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {
            
            //进入If块说明解析数据肯定是失败的,这时候要把解析错误信息放到error里。
            //如果数据长度大于0,而且有响应url
            if ([data length] > 0 && [response URL]) {
                
                //错误信息字典,填充一些错误信息
                NSMutableDictionary *mutableUserInfo = [@{
                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                          NSURLErrorFailingURLErrorKey:[response URL],
                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                        } mutableCopy];
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }

                //生成错误
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }
            
            //返回标识
            responseIsValid = NO;
        }

        //判断自己可接受的状态吗
        //如果和response的状态码不匹配,则进入if块
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            //填写错误信息字典
            NSMutableDictionary *mutableUserInfo = [@{
                                               NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                               NSURLErrorFailingURLErrorKey:[response URL],
                                               AFNetworkingOperationFailingURLResponseErrorKey: response,
                                       } mutableCopy];

            if (data) {
                mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
            }

            //生成错误
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
            //返回标识
            responseIsValid = NO;
        }
    }

    //给我们传过来的错误指针赋值
    if (error && !responseIsValid) {
        *error = validationError;
    }

    //返回是否错误标识
    return responseIsValid;
}
AFJSONResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //先判断是不是可接受类型和可接受code
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //error为空,或者有错误,去函数里判断。
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            //返回空
            return nil;
        }
    }

    id responseObject = nil;
    NSError *serializationError = nil;
    // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
    // See https://github.com/rails/rails/issues/1742
    
    //如果数据为空
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    //不空则去json解析
    if (data.length > 0 && !isSpace) {
        responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    } else {
        return nil;
    }

    //判断是否需要移除Null值
    if (self.removesKeysWithNullValues && responseObject) {
        responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }
    
    //拿着json解析的error去填充错误信息
    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    //返回解析结果
    return responseObject;
}

注释写的很清楚,大概需要讲一下的是以下几个函数:

//1
AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain))
//2
AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
//3
AFErrorWithUnderlyingError(serializationError, *error);

之前注释已经写清楚了这些函数的作用,首先来看第1个:

//判断是不是我们自己之前生成的错误信息,是的话返回YES
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    //判断错误域名和传过来的域名是否一致,错误code是否一致
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
        
    }
    //如果userInfo的NSUnderlyingErrorKey有值,则在判断一次。
    else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

这里可以注意,我们这里传过去的code和domain两个参数分别为NSURLErrorCannotDecodeContentDataAFURLResponseSerializationErrorDomain,这两个参数是我们之前判断response可接受类型和code时候自己去生成错误的时候填写的。

第二个:

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    //分数组和字典
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        
        //生成一个数组,只需要JSONObject.count个,感受到大神写代码的严谨态度了吗...
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            //调用自己
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }
        //看我们解析类型是mutable还是非muatable,返回mutableArray或者array
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
        
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            //value空则移除
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                //如果数组还是去调用自己
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }
        
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

方法主要还是通过递归的形式实现。比较简单。

第三个:

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }
    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

方法主要是把json解析的错误,赋值给我们需要返回给用户的error上。比较简单,小伙伴们自己看看就好。

至此,AFJSONResponseSerializer就讲完了。
而我们ResponseSerialize还有一些其他的类型解析,大家可以自行去阅读,代码还是很容易读的,在这里就不浪费篇幅去讲了。


分割图.png

在AFURLSessionManager中,有这么一个类:_AFURLSessionTaskSwizzling。这个类大概的作用就是替换掉NSUrlSession中的resumesuspend方法。正常处理原有逻辑的同时,多发送一个通知,以下是我们需要替换的新方法:


//被替换掉的方法,只要有TASK开启或者暂停,都会执行
- (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];
    }
}
- (void)af_suspend {
    
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_suspend];
    
    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}

这块知识是关于OC的Runtime:method swizzling的,如果有不清楚的地方,可以看看这里method swizzling--by冰霜或者自行查阅。

+ (void)load {
 
    if (NSClassFromString(@"NSURLSessionTask")) {
        
        // 1) 首先构建一个NSURLSession对象session,再通过session构建出一个_NSCFLocalDataTask变量

        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        // 2) 获取到af_resume实现的指针
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        Class currentClass = [localDataTask class];
        
        // 3) 检查当前class是否实现了resume。如果实现了,继续第4步。
        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            
            // 4) 获取到当前class的父类(superClass)
            Class superClass = [currentClass superclass];
            
            // 5) 获取到当前class对于resume实现的指针
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
            
            //  6) 获取到父类对于resume实现的指针
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
 
               // 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                //执行交换的函数
                [self swizzleResumeAndSuspendMethodForClass:currentClass];
            }
            // 8) 设置当前操作的class为其父类class,重复步骤3~8
            currentClass = [currentClass superclass];
        }
        
        [localDataTask cancel];
        [session finishTasksAndInvalidate];
    }
}

原方法中有大量的英文注释,我把它翻译过来如下:

iOS 7和iOS 8在NSURLSessionTask实现上有些许不同,这使得下面的代码实现略显trick
关于这个问题,大家做了很多Unit Test,足以证明这个方法是可行的
目前我们所知的:

一些假设前提:

其余的一部分翻译在注释中,对应那一行代码。大概总结下这个注释:

但是有几个点大家可能会觉得疑惑的,我们先把这个方法调用的替换的函数一块贴出来。

//其引用的交换的函数:
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
}
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));
}

因为有小伙伴问到过,所以我们来分析分析大家可能会觉得疑惑的地方:

  1. 首先可以注意class_getInstanceMethod这个方法,它会获取到当前类继承链逐级往上,第一个实现的该方法。所以说它获取到的方法不能确定是当前类还是父类的。而且这里也没有用dispatch_once_t来保证一个方法只交换一次,那万一这是父类的方法,当前类换一次,父类又换一次,不是等于没交换么?...请注意这行判断:
// 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) { 
          //执行交换的函数
         [self swizzleResumeAndSuspendMethodForClass:currentClass]; 
}

这个条件就杜绝了这种情况的发生,只有当前类实现了这个方法,才可能进入这个if块。

2.那iOS7两个类都交换了af_resume,那岂不是父类换到子类方法了?...只能说又是没仔细看代码的...注意AF是去向当前类添加af_resume方法,然后去交换当前类的af_resume。所以说根本不会出现这种情况...

AFUrlSessionManager 基本上就这么多内容了。

分割图.png

现在我们回到一开始初始化的这行代码上:

self.operationQueue.maxConcurrentOperationCount = 1;

1)首先我们要明确一个概念,这里的并发数仅仅是回调代理的线程并发数。而不是请求网络的线程并发数。请求网络是由NSUrlSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接收数据。这些线程是并发的

2)明确了这个概念之后,我们来梳理一下AF3.x的整个流程和线程的关系:

3)最后我们来解释解释为什么回调Queue要设置并发数为1:

当然这仅仅是我个人的看法,如果有不同意见的欢迎交流~

至此我们AF3.X业务层的逻辑,基本上结束了。小伙伴们,看到这你明白了AF做了什么了吗?可能很多朋友要扔鸡蛋了...可能你还是没觉得AF到底有什么用,我用NSUrlSession不也一样,我干嘛要用AF,在这里,我暂时卖个关子,等我们下篇讲完AFSecurityPolicy和部分UIKit的扩展,以及AF2.x的核心类源码实现之后,我们再好好总结。

后续文章:

AFNetworking之于https认证
AFNetworking之UIKit扩展与缓存实现
AFNetworking到底做了什么?(终)

上一篇 下一篇

猜你喜欢

热点阅读