探秘AFNetworking
该文章属于<简书 — 刘小壮>原创,转载请注明:
封面图
AFNetworking源码分析
AFNetworking
是iOS
最常用的网络框架,虽然系统也有NSURLSession
,但是我们一般不会直接用它。AFNetworking
经过了三个大版本,现在用的大多数都是3.x的版本。
AFNetworking
经历了下面三个阶段的发展:
- 1.0版本 : 基于
NSURLConnection
的封装。 - 2.0版本 : 两套实现,分别基于
NSURLConnection
和NSURLSession
,是转向NSURLSession
的过渡版。 - 3.0版本 : 基于
NSURLSession
的封装。
文件构成
文件构成AFNetworking3.X
的构成很简单,主要就四部分,除此之外还有一些基于UIKit
的Category
,但这些并不是标配。
- Manager : 负责处理网络请求的两个
Manager
,主要实现都在AFURLSessionManager
中。 - Reachability : 网络状态监控。
- Security : 处理网络安全和
HTTPS
相关的。 - Serialization : 请求和返回数据的格式化器。
AFURLSessionManager
在AFN3.0
中,网络请求的manager
主要有AFHTTPSessionManager
和AFURLSessionManager
构成,二者为父子关系。这两个类职责划分很清晰,父类负责处理一些基础的网络请求代码,并且接受NSURLRequest
对象,而子类则负责处理和http
协议有关的逻辑。
AFN
的这套设计很便于扩展,如果以后想增加FTP
协议的处理,则基于AFURLSessionManager
创建子类即可。子类中只需要进行很少的代码处理,创建一个NSURLRequest
对象后调用父类代码,由父类去完成具体的请求操作。
创建sessionManager
AFHTTPSessionManager
类的初始化方法中并没有太多实现代码,其内部调用的都是父类AFURLSessionManager
的initWithSessionConfiguration
方法,下面是此方法内部的一些关键代码。
在初始化方法中包含一个参数sessionConfiguration
,如果没有传入的话默认是使用系统的defaultConfiguration
,我们创建是一般都不会自定义configuration
,所以大多数都是系统的。
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
随后是NSURLSession
的初始化代码,关于NSOperationQueue
后面详细进行讲解。NSURLSession
的初始化方式有两种,一种是使用系统的共享session
,另一种是自己创建session
。AFN
选择的是创建自己的session
,并且每个请求都会创建一个独立的session
。
可以通过NSURLSession
进行连接复用,这样可以避免很多握手和挥手的过程,提高网络请求速度,苹果允许iOS
设备上一个域名可以有四个连接同时存在。但是由于AFN
的实现是每个请求都创建一个session
,所以就不能进行连接复用。
所以可以通过在外面对AFN
进行二次封装,将AFHTTPSessionManager
复用为单例对象,通过复用sessionManager
的方式,来进行连接的复用。但是这种方案对于不同的requestSerializer
、responseSerializer
等情况,还是要做特殊兼容,所以最好建立一个sessionManager
池,对于同类型的sessionManager
直接拿出来复用,否则就创建新的。
// 共享session连接池
[NSURLSession sharedSession];
// 创建新session,则不能使用共享session连接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
由于当前AFURLSessionManager
对象的所有sessionTask
请求任务,都是共享同一个回调代理的,所以AFN
为了区分每个sessionTask
,通过下面的可变字典,将所有taskDelegate
和task.taskIdentifier
的进行了一一对应,以便于很容易的对每个请求task
进行操作。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
在初始化方法中,可以发现AFN
在创建session
后,调用了getTasksWithCompletionHandler
方法来获取当前所有的task
。但是现在刚创建session
,理论上来说是不应该有task
的。但从AFN
的issues
中找到了答案issues 3499。
这是因为,在completionHandler
回调中,为了防止进入前台时,通过session id
恢复的task
导致一些崩溃问题,所以这里将之前的task
进行遍历,并将回调都置nil
。
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
创建task
在AFURLSessionManager
中进行task
的创建,task
的类型总共分为三种,dataTask
、uploadTask
、downloadTask
,AFN
并没有对streamTask
进行处理。
AFHTTPSessionManager
在创建GET
、POST
等请求时,本质上都是调用了下面的方法或其类似的方法,方法内部会创建一个task
对象,并调用addDelegateForDataTask
将后面的处理交给AFURLSessionManagerTaskDelegate
来完成。随后会将task
返回给调用方,调用方获取到task
对象后,也就是子类AFHTTPSessionManager
,会调用resume
方法开始请求。
- (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 {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
除了普通请求外,upload
、download
都有类似的处理。
在addDelegateForDataTask
方法中,会调用sessionManager
的setDelegate:forTask:
方法,此方法内部将task
和taskDelegate
进行了注册。由于AFN
可以通过通知让外界监听请求状态,所以在此方法中还监听了task
的resume
和suspend
事件,并在实现代码中将事件广播出去。
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
如果从AFHTTPSessionManager
的创建任务开始,按代码逻辑跟到这里,发现其实AFN3.0
的请求代码真的很简单,主要都集中在创建NSMutableURLRequest
那里,其他都依赖于NSURLSession
,因为确实NSURLSession
的API
封装程度比较好,也很好使用。
AFN3.0
的作用就是对NSURLSession
的封装性比较好,你不用去写太多重复性的代码,并且可以很容易的通过block
得到回调结果。
AFURLSessionManagerTaskDelegate
NSURLSession
的回调方法比较多,这里只针对一些关键代码进行讲解,以及梳理整体回调逻辑,不一一列举每个回调方法的作用,详细源码各位可以直接下载AFN
代码查看。
在AFURLSessionManager
中,有一个AFURLSessionManagerTaskDelegate
类比较重要,这个类和sessionTask
是一一对应的,负责处理sessionTask
请求的很多逻辑,NSURLSessionDelegate
的回调基本都转发给taskDelegate
去处理了。在NSURLSession
回调中处理了HTTPS
证书验证、下载进度之类的,没有太复杂的处理。
taskDelegate
的设计很不错,可以将代理回调任务处理对象化,也可以给AFURLSessionManager
类瘦身。比较理想的是直接将代理设置为taskDelegate
,但是由于会涉及一些AFURLSessionManager
自身的处理逻辑,所以才设计为消息传递的方式。
taskDelegate
的功能很简单,主要是NSData
数据的处理,NSProgress
上传下载进度的处理,以及通知参数的处理。在进行AFN
的下载处理时,NSData
的数据拼接、事件回调,及文件处理,都是由taskDelegate
来完成的。
下面是downloadTask
任务完成时的处理代码,其他回调代码就不一一列举了。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
taskDelegate
中有一个很好的设计,taskDelegate
并不直接在NSURLSession
的代理方法中做进度拼接和回调。而是对于上传和下载任务分别对应不同的NSProgress
,并通过KVO
来监听fractionCompleted
属性,并且实现cancel
、suspend
等状态回调。任务的状态和进度处理交给NSProgress
,在回调方法中直接拼接NSProgress
的进度,从而回调KVO
方法。
NSProgress
内部的cancel
、pause
、resume
方法,正好可以对应到sessionTask
的方法调用。但是从代码角度来看,AFN
好像并没有进行相关的调用,但这个设计思路很好。
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
#if AF_CAN_USE_AT_AVAILABLE
if (@available(iOS 9, macOS 10.11, *))
#else
if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
{
progress.resumingHandler = ^{
[weakTask resume];
};
}
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
_AFURLSessionTaskSwizzling
看过源码的话,可以发现AFURLSessionManager
中还有一个_AFURLSessionTaskSwizzling
类,这里我们简称taskSwizzling
类。我认为此类的设计实在是冗余,此类的主要功能就是在+load
方法中进行一个swizzling
,将dataTask
的resume
和suspend
方法进行替换,并且在替换后的方法中发出对应的通知,并没有太多实际的功能。
只不过taskSwizzling
类中还是有一些不错的代码设计值得借鉴的,由于sessionTask
存在一系列继承链,所以直接对其进行swizzling
对其他子类并不生效,因为每个子类都有自己的实现,而写一大堆swizzling
又没有什么技术含量。
在iOS7
和iOS8
上,sessionTask
的继承关系并不一样,最好进行一个统一的处理。AFN
采取的方式是创建一个dataTask
对象,并对这个对象进行swizzling
,并且遍历其继承链一直进行swizzling
,这样保证集成继承链的正确性。
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
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
+ (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));
}
}
clang预编译指令
AFN
为了避免发生编译器警告,采取了预编译指令对代码进行修饰,预编译指令基本由三部分组成,push
、pop
、ignored
类型。Github
上有人维护了一份clang warning清单,如果想进行对应的预编译处理可以上去找找有没有合适的。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
线程问题
NSURLSession
在iOS8
以下会并发创建多个task
,但并发设置task identifier
的时候会存在identifier
重复的问题。为了解决这个问题,在iOS8
以下,系统将所有sessionTask
的创建都放在一个同步的串行队列中进行,保证创建及赋值操作是串行进行的。
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});
// 如果Foundation版本小于iOS8,则把block任务放在一个同步队列中执行。这个问题是由于在iOS8以下并发创建任务,可能会有多个相同的identifier
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
一个比较有意思的是,AFN
为了让开发者明白为什么要加这个判断,对iOS8
系统的判断定义成了一个宏,并且用Apple Support
的id
作为宏定义命名,很见名知意。
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
AFN
在回调didCompleteWithError
方法,并处理返回数据时,会切换到其他线程和group
去处理,处理完成后再切换到主线程并通知调用方。
AFN
提供了两个属性,用来设置请求结束后进行回调的dispatch queue
和dispatch group
,如果不设置的话,AFN
会有默认的实现来处理请求结束的操作。下面是group
和queue
的实现,AFN
对于返回数据的处理,采用的是并发处理。
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;
}
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;
}
NSOperationQueue
AFN
在创建AFURLSessionManager
的operationQueue
时,将其最大并发数设置为1。这是因为在创建NSURLSSession
时,苹果要求网络请求回来的数据顺序执行,为了保证代理方法的执行顺序,所以需要串行的调用NSURLSSession
的代理方法。
AFHTTPSessionManager
AFHTTPSessionManager
本质上是对父类AFURLSessionManager
的封装,主要实现都在父类中,自己内部代码实现很简单。在创建AFHTTPSessionManager
时会传入一个baseURL
,以及指定requestSerializer
、responseSerializer
对象。
从代码实现来看,AFN
的请求并不是单例形式的,每个请求都会创建一个新的请求对象。平时调用的GET
、POST
等网络请求方法,都定义在AFHTTPSessionManager
中。AFHTTPSessionManager
内部则调用父类方法,发起响应的请求并获取到task
对象,调用task
的resume
后返回给调用方。
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
AFURLRequestSerialization
AFURLRequestSerialization
负责创建NSMutableURLRequest
请求对象,并对request
进行请求的参数拼接、设置缓存策略、设置请求头等关于请求相关的配置。
AFURLRequestSerialization
并不是一个类,而是一个文件,其中包含三个requestSerializer
请求对象,分别对应着不同的请求序列化器。
- AFHTTPRequestSerializer:普通请求。
- AFJSONRequestSerializer:
JSON
请求。 - AFPropertyListRequestSerializer:一种特殊的
xml
格式请求。
这三个类区别就在于Content-Type
不同,其他基本都是一样的。AFN
默认是HTTP
的。
AFURLRequestSerialization协议
在文件中定义了同名的AFURLRequestSerialization
协议,不同的requestSerializer
会对协议方法有不同的实现,下面是AFHTTPRequestSerializer
的实现代码。其核心代码实现也比较直观,就是在创建requestSerializer
的时候,设置请求头的公共参数,以及将请求参数通过NSJSONSerialization
转换为NSData
,并将其赋值给request
对象的httpBody
,下面是精简后的核心代码。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
[mutableRequest setHTTPBody:jsonData];
}
return mutableRequest;
}
如果想给网络请求设置请求参数的话,需要通过requestSerializer
对外暴露的API
添加参数,AFN
的requestManager
并不直接对外提供设置请求头的代码。通过requestSerializer
可以对请求头进行添加和删除、以及清空的操作。
从创建AFURLRequestSerialization
对象到最后返回NSURLRequest
对象,中间的过程并不复杂,主要是设置请求头和拼接参数,逻辑很清晰。
AFQueryStringPair
AFURLRequestSerialization
有一个很重要的功能就是参数处理,AFQueryStringPair
就是负责处理这些参数的。pair
类中定义了两个属性,分别对应请求参数的key
、value
。除此之外,还定义了一些非常实用的C语言函数。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (id)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
AFQueryStringFromParameters
函数负责将请求参数字典,转成拼接在URL
后面的参数字符串,这个函数是AFQueryStringPair
类中定义的一个关键函数。函数内部通过AFQueryStringPairsFromDictionary
函数将参数字典,转为存储pair
对象的数组并进行遍历,遍历后调用URLEncodedStringValue
方法对参数进行拼接,最后成为字符串参数。
URLEncodedStringValue
方法实现很简单,就是进行一个key
、value
的拼接,并且在中间加上“=”。
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
下面是参数拼接的代码,函数内部会将原有的参数,转换为AFQueryStringPair
对象的类型,但之前的层级结构不变。这句话是什么意思呢,就是说对原有传入的对象进行逐层递归调用,并且将最后一层字典的key
、value
参数,转成pair
类型的对象,并且将嵌套有pair
对象的数组返回给调用方。
对象层级不变,但字典、集合都会被转换为数组结构,也就是之前传入字典、数组、字典的嵌套结构,返回的时候就是数组、数组、pair
的结构返回。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
设置NSMutableURLRequest
AFHTTPRequestSerializer
在创建NSMutableURLRequest
时,需要为request
设置属性。serializer
对外提供了和request
同名的一些属性,外界直接调用serializer
即可设置request
的属性。
AFHTTPRequestSerializer
内部创建request
时,并不是根据设置request
的属性按个赋值,而是通过一个属性数组AFHTTPRequestSerializerObservedKeyPaths
,将serializer
需要赋值给request
的属性,都放在数组中。
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
在初始化AFHTTPRequestSerializer
时,遍历keyPath
数组并通过KVO
的方式,监听serializer
的赋值。如果外界对serializer
对应的属性进行赋值,则将其添加到mutableObservedChangedKeyPaths
数组中。在创建request
对象是,遍历mutableObservedChangedKeyPaths
数组并将值赋值给request
对象。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
表单提交
当进行POST
表单提交时,需要用到AFMultipartFormData
协议。调用POST
方法后,会回调一个遵守此协议的对象,可以通过此对象进行表单提交操作。
[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:params[@"front_img"]
name:@"front_img"
fileName:frontImgfileName
mimeType:@"multipart/form-data"];
[formData appendPartWithFileData:params[@"reverse_img"]
name:@"reverse_img"
fileName:reverseImgfileName
mimeType:@"multipart/form-data"];
[formData appendPartWithFileData:params[@"face_img"]
name:@"face_img"
fileName:faceImgfileName
mimeType:@"multipart/form-data"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
// nothing
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// nothing
} failure:nil];
进行表单提交时,可以直接传入文件,也可以传入路径。表单提交可以同时提交多个文件,理论上数量不受限制。
缓存策略
AFN
的缓存策略和NSURLCache
的缓存策略一致,并且直接使用系统的枚举,这对iOS
开发者是非常友好的。下面是枚举定义,忽略掉一些unimplemented
的,和一些重定向到已有枚举的,可用的都在这。
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
NSURLRequestReloadRevalidatingCacheData = 5,
};
- NSURLRequestUseProtocolCachePolicy,使用协议指定的缓存策略。
- NSURLRequestReloadIgnoringLocalCacheData,忽略缓存,直接发起请求。
- NSURLRequestReturnCacheDataElseLoad,不验证缓存过期时间,如果有则使用缓存数据,如果不存在则请求服务器。
- NSURLRequestReturnCacheDataDontLoad,不验证缓存过期时间,如果有则使用缓存数据,如果不存在则请求失败。
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData,忽略本地缓存,以及代理等中间介质的缓存。
- NSURLRequestReloadRevalidatingCacheData,和数据的源服务器验证数据合法性,如果可以用就直接使用缓存数据,否则从服务器请求数据。
AFURLResponseSerialization
AFURLResponseSerialization
负责处理response
相关的逻辑,其功能主要是设置acceptType
、编码格式和处理服务器返回数据。同样的,AFURLResponseSerialization
也有同名的协议,每个子类都遵循代理方法并实现不同的返回值处理代码。
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
和AFURLRequestSerialization
一样,AFURLResponseSerialization
由一个父类和六个子类构成,子类中有一个是Mac
的,所以这里不做分析,子类的职责只是对acceptType
做修改以及处理具体的返回数据。
- AFHTTPResponseSerializer:公共父类,处理返回值类型为
NSData
二进制。 - AFJSONResponseSerializer:
JSON
返回数据,也是默认类型。 - AFXMLParserResponseSerializer,处理
XML
返回数据,由系统NSXMLParser
负责处理。 - AFPropertyListResponseSerializer:处理特殊
XML
返回数据,也就是plist
数据。 - AFImageResponseSerializer:处理图片返回数据,这个类型用的也比较多。
- AFCompoundResponseSerializer:处理复杂数据,返回结果类型有多种。
容错处理
由于服务器有时候会返回null
的情况,系统会将其转换为NSNull
对象,而对NSNull
对象发送不正确的消息,就会导致崩溃。从服务器接收到返回值后,AFN
会对返回值进行一个递归查找,找到所有NSNull
对象并将其移除,防止出现向NSNull
对象发送消息导致的崩溃。
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
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];
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;
}
AFNetworking的设计技巧
bundleForClass
在使用NSBundle
对象时,我们最常用的就是mainBundle
或者bundleWithPath
这种方式获取bundle
,这种对于都是从app
二进制读取的时候是没有问题的。但是如果涉及到framework
动态库,就不是那么易于使用。
framework
中可以包含资源文件,例如.bundle
文件。如果是动态库形式的framework
(framework
也有静态形式),其会以一个独立二进制的形式表现,并且会分配独立的二进制空间。在读取bundle
的时候,就可以考虑使用bundleForClass
的方式读取。
bundleForClass
表示从当前类定义的二进制,所在的程序包中读取NSBundle
文件。例如.app
就是从main bundle
中读取,如果是framework
就从其所在的二进制中读取。
网络指示器
AFN
提供了一些UIKit
的Category
,例如网络请求发起时,网络指示器转菊花,则由AFNetworkActivityIndicatorManager
类负责。开启网络指示器很简单,添加下面代码即可,网络指示器默认是关闭的。
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
这里不对AFNetworkActivityIndicatorManager
的代码进行过多的分析,只是调其中比较重要的点来分析,下面统称为indicatorManager
。
之前在_AFURLSessionTaskSwizzling
类中写了很多代码,就是为了发出resume
和suspend
两个通知,这两个通知在indicatorManager
中就用到了。网络指示器监听了下面的三个通知,并且完全由通知来驱动。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
如果看indicatorManager
中的源码,你会发现为什么里面还有timer
,完全不需要啊,有网络请求就转菊花,没网络请求就停止不就行了吗?
这是因为AFN
考虑,如果一个网络请求很快的话,会导致菊花出现转一下很快就消失的情况,如果网络请求比较多会多次闪现。所以对于这个问题,indicatorManager
通过Timer
的方式实现,如果在指定的区间内网络请求已经结束,则不在显示菊花,如果有多次请求则在请求之间也不进行中断。
对于开始转圈设置的是1.0秒,结束转圈设置的是0.17秒。也就是当菊花开始旋转时,需要有1.0秒的延时,这个时间足以保证之前的菊花停止转动。结束转圈则会在0.17秒之后进行,可以保证菊花的旋转至少会有0.17秒。
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
- (void)startActivationDelayTimer {
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)startCompletionDelayTimer {
[self.completionDelayTimer invalidate];
self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
由于indicatorManager
是采用通知的方式进行回调,所有的网络请求通知都会调到这。所以当多个网络请求到来时,会通过一个_activityCount
来进行计数,可以将其理解为一个队列,这样更容易理解。在显示网络指示器的时候,就是基于_activityCount
来进行判断的,如果队列中有请求则显示网络指示器,无论有多少请求。
这种设计思路比较好,在项目中很多地方都可以用到。例如有些方法需要成对进行调用,例如播放开始和暂停,如果某一个方法调用多次就会造成bug
。这种方式就比较适合用count
的方式进行容错,内部针对count
做一些判断操作。
- (void)incrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount = MAX(_activityCount - 1, 0);
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
indicatorManager
是多线程安全的,在一些关键地方都通过synchronized
的方式加锁,防止从各个线程调用过来的通知造成资源抢夺的问题。
AFSecurityPolicy
验证处理
AFN
支持https
请求,并通过AFSecurityPolicy
类来处理https
证书及验证,但其https
请求的执行还是交给NSURLSession
去完成的。
下面是NSURLSession
的一个代理方法,当需要进行证书验证时,可以重写此方法并进行自定义的验证处理。验证完成后通过completionHandler
的block
来告知处理结果,并且将验证结果disposition
和公钥credential
传入。
AFN
通过AFSecurityPolicy
类提供了验证逻辑,并且在内部可以进行证书的管理。也可以不使用AFN
提供的验证逻辑,重写sessionDidReceiveAuthenticationChallenge
的block
即可自定义验证逻辑,不走AFN
的逻辑。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
除了进行NSURLSession
请求验证的回调,对于每个task
也有对应的代理方法。两个代理方法内部实现基本一样,区别在于对于每个task
,AFN
提供了taskDidReceiveAuthenticationChallenge
回调block
,可以由外界自定义证书验证过程。
验证结果是通过一个枚举回调给NSURLSession
的,参数是一个NSURLSessionAuthChallengeDisposition
类型的枚举,表示证书验证的情况,此枚举包含下面几个具体值。
- NSURLSessionAuthChallengeUseCredential
使用当前证书建立SSL
连接,并处理后续请求 - NSURLSessionAuthChallengePerformDefaultHandling
使用默认的处理方式,当前证书被忽略 - NSURLSessionAuthChallengeCancelAuthenticationChallenge
验证不通过,取消整个网络请求 - NSURLSessionAuthChallengeRejectProtectionSpace
这次验证被忽略,但不取消网络请求
Security
HTTPS
请求的密钥管理等安全相关的处理,都放在Security.framework
框架中。在AFSecurityPolicy
中经常可以看到SecTrustRef
类型的变量,其表示的就是密钥对象,其中包含了公钥等信息。
我们可以通过下面的命令获取到公钥,具体格式这里不做过多介绍,详细的可以Google一下公钥格式。
// 获取公钥命令
SecTrustCopyPublicKey(serverTrust)
// 打印的公钥(公钥已做脱敏)
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442DFBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43608D6F494360FFDEE83AD1DCE120D6F1, addr: 0x280396dc0>
AFSecurityPolicy概述
AFSecurityPolicy
的职责比较单一,只处理公钥和验证的逻辑,其定义是一个单例对象。此类主要由四个属性和一个方法构成。
// 证书验证方式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 本地自签名证书集合
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否验证证书的合法性(是否允许自签名证书)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证域名是否有效
@property (nonatomic, assign) BOOL validatesDomainName;
如果进行细分的话,AFSecurityPolicy
的功能基本就两个。一个是通过CA
的方式进行验证,另一个就是进行SSL Pinning
自签名验证。evaluateServerTrust:forDomain:
是AFSecurityPolicy
最主要的方法,用来进行证书的合法性验证。
SSL Pinning
AFSecurityPolicy
进行SSL Pinning
验证的方式分为以下三种,如果是None
则会执行正常CA
验证的流程,其他两种都是自签名的流程。AFN
中默认的调用是defaultPolicy
方法,其内部设置的是AFSSLPinningModeNone
模式。
- AFSSLPinningModeNone
正常流程,通过CA
机构颁发的公钥,对服务器下发的证书验证数字签名,并且获得公钥。 - AFSSLPinningModeCertificate
不通过CA
的流程进行验证,而是通过本地内置的服务端证书进行验证,验证过程分为两步。首先验证证书是否过期或失效,其次验证本地是否包含此证书。 - AFSSLPinningModePublicKey
不进行CA
的验证,也不验证证书,只验证公钥是否有效。
对于本地自签名证书的管理有两种方式,一种是默认会在本地查找遍历所有.cer
的文件,并存在一个自签名证书的集合中。也可以在创建AFSecurityPolicy
对象时传入SSLPinningMode
,下面是查找本地.cer
文件的逻辑。
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
return [NSSet setWithSet:certificates];
}
自签名证书
HTTPS
在进行握手时,需要通过CA
的公钥进行验证,保证服务器公钥的合法性,没有被篡改为有问题的公钥。如果使用CA
机构颁发的证书,无论使用NSURLSession
还是AFNetworking
都不需要修改代码,这些都会自动完成。如果不想使用CA
的证书验证,例如自签名证书在CA
证书验证时就会失败。
这种情况可以使用自签名的方式进行验证,也就是在客户端本地内置一份证书,服务器进行四次握手时,通过保存在本地的证书和服务器进行对比,证书相同则表示验证成功,不走CA
的验证方式。
AFN
为我们提供了自签名证书的验证方法,通过SSLPinningMode
设置验证方式为自签名,并且传入证书集合。如果没有传入证书集合,则AFN
默认会遍历整个沙盒,查找所有.cer
的证书。
进行沙盒验证时,需要将AFSecurityPolicy
的allowInvalidCertificates
设置为YES
,默认是NO
,表示允许无效的证书,也就是自签名的证书。
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
securityPolicy.allowInvalidCertificates = YES;
AFNetworking 2.x
AFNetworking2.x
由NSURLSession
和NSURLConnection
两部分组成,并且分别对应不同的类,这里主要介绍NSURLConnection
部分的源码实现。
总体概览
NSURLConnection
实现由下面三个类构成,从源码构成可以看出,无论是session
还是connection
方案,都具有很好的扩展性。例如这里AFHTTPRequestOperation
是基于AFURLConnectionOperation
实现的,如果需要实现FTP
协议,则可以创建一个继承自AFURLConnectionOperation
的AFFPTConnectionOperation
类并重写对应方法即可。
- AFURLConnectionOperation
继承自NSOperation
,负责网络请求的逻辑实现,每个网络请求就是一个Operation
对象。 - AFHTTPRequestOperation
继承自AFURLConnectionOperation
,处理HTTP
相关网络请求。 - AFHTTPRequestOperationManager
内部持有一个NSOperationQueue
,负责管理所有Operation
网络请求。
AFURLConnectionOperation
下面是AFURLConnectionOperation
的初始化方法,和AFURLSessionManager
有些不一样。其内部增加了状态的概念,以及RunloopMode
的概念,这两个我们后面会详细讲解。shouldUseCredentialStorage
表示是否由系统做证书验证,后面设置了securityPolicy
,和sessionManager
一样也是使用默认方案。
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
_state = AFOperationReadyState;
self.lock = [[NSRecursiveLock alloc] init];
self.lock.name = kAFNetworkingLockName;
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.request = urlRequest;
self.shouldUseCredentialStorage = YES;
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
}
AFURLConnectionOperation
继承自NSOperation
,由于NSOperation
和网络请求的过程很像,有开始、暂停、完成等,并且很好的支持KVO
监听,所以AFN
将每个网络请求都当做一个Operation
任务。AFURLConnectionOperation
可以设置任务优先级,也可以通过AFHTTPRequestOperationManager
设置最大并发数,基本上NSOperationQueue
提供的功能都能用。
在AFHTTPRequestOperationManager
中定义了NSOperationQueue
,创建网络请求任务后,都会被加入到queue
中,随后由系统调用queue
中的operation
任务,执行operation
的start
方法发起请求。AFURLConnectionOperation
只需要在内部实现start
、pause
、resume
等父类方法即可,其他都由系统去进行调用。这种设计可以很好的将manager
和operation
进行解耦,二者不用直接发生调用关系。
NSURLConnection
中,每个网络请求对应一个AFHTTPRequestOperation
,所有网络请求都共用一个manager
来管理operation
。而AFHTTPSessionManager
则不同,每个网络请求对应一个manager
以及一个task
。
state设计
AFURLConnectionOperation
支持KVO
的方式,让外界监听网络请求的变化,并通过重写setState
方法,在内部加入willChangeValueForKey
触发KVO
回调。AFN
通过AFOperationState
来管理网络请求状态,下面是AFN
对其的状态定义。
- AFOperationPausedState
请求暂停 - AFOperationReadyState
请求已准备好 - AFOperationExecutingState
请求正在执行中 - AFOperationFinishedState
请求完成
当网络请求状态发生改变时,都会调用setState
方法进行赋值,例如下面是请求完成时的处理代码。除此之外,当判断AFN
请求状态时,也是通过这个属性作为判断依据的。
- (void)finish {
[self.lock lock];
self.state = AFOperationFinishedState;
[self.lock unlock];
}
常驻线程
AFURLConnectionOperation
中设计了常驻线程,并且重写了operation
的start
等方法,网络请求的start
、cancel
、pause
等操作,都是在常驻线程中完成的。网络请求结束后,数据回调的处理也是在这个线程中完成的。
这是因为在哪个线程创建NSURLConnection
对象并发出请求,则数据返回时也默认从那个线程接受数据。如果请求都是从主线程发出的,请求返回时如果屏幕正在滑动,runloopMode
为UITrackingRunLoopMode
则不能处理返回数据。而如果把网络请求都加到主线程的NSRunLoopCommonModes
中,在大量网络请求返回时,处理返回数据会影响屏幕滑动FPS
。
所以为了保证网络请求数据可以正常返回并被处理,而又不影响屏幕FPS
,则用一个单独的线程来处理。如果每个请求都对应一个线程来处理返回任务,会造成大量线程的占用,所以用一个常驻线程来处理所有网络请求,来保证线程资源的最小占用。常驻线程实际上是一个单例线程,并且这个单例线程被加入了一个Port
进行保活,保证线程可以不被退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[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;
}
通过AFURLConnectionOperation
发起网络请求时,实际创建connection
对象的代码在下面的方法中。在创建connection
对象后,并没有立即发出网络请求,而是将startImmediately
设置为NO
。随后会设置NSURLConnection
和NSOutputStream
的RunloopMode
,网络请求会从单例线程的runLoopModes
中发出,这样当网络请求返回时,回调代码也会在runLoopModes
中去执行。
operationDidStart
方法中会调用NSURLConnection
的scheduleInRunLoop:forMode:
方法,将网络请求任务派发到Runloop
指定的Mode
中。我觉得给Operation
设置runLoopModes
其实意义不大,因为常驻线程基本上只会有一个Mode
,也就是NSRunloopDefaultMode
,基本上不会有其他Mode
,所以这里设置runLoopModes
没什么意义。
- (void)operationDidStart {
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for (NSString *runLoopMode in self.runLoopModes) {
[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
}
[self.outputStream open];
[self.connection start];
}
代理方法
AFURLConnectionOperation
是通过NSURLConnection
实现网络请求的,这里简单讲一下operation
中代理方法的实现。
AFN
实现了https
证书验证的代码,具体实现和AFURLSessionManager
基本类似,并且也是通过AFSecurityPolicy
来处理具体的证书验证逻辑。
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
关于请求服务器数据这块值得讲一下,在NSURLConnection
接收服务器数据时,AFN
通过创建了一个outputStream
,来承载和组织具体的数据,并且在内存中进行存储。当没有可用空间或发生其他错误时,会通过streamError
的方式进行体现。
当网络请求结束时,会调用didFinishLoading
方法,AFN
会从outputStream
中拿出数据并赋值给responseData
,当做返回值数据使用。
- (void)connection:(NSURLConnection __unused *)connection
didReceiveData:(NSData *)data
{
NSUInteger length = [data length];
while (YES) {
NSInteger totalNumberOfBytesWritten = 0;
if ([self.outputStream hasSpaceAvailable]) {
const uint8_t *dataBuffer = (uint8_t *)[data bytes];
NSInteger numberOfBytesWritten = 0;
while (totalNumberOfBytesWritten < (NSInteger)length) {
numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
if (numberOfBytesWritten == -1) {
break;
}
totalNumberOfBytesWritten += numberOfBytesWritten;
}
break;
} else {
[self.connection cancel];
if (self.outputStream.streamError) {
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
}
return;
}
}
if (self.downloadProgress) {
self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
}
}
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
[self.outputStream close];
if (self.responseData) {
self.outputStream = nil;
}
self.connection = nil;
[self finish];
}
有outputStream
,也会有与之对应的inputStream
,inputStream
实现很简单,就是修改NSMutableURLRequest
的HTTPBodyStream
。
- (NSInputStream *)inputStream {
return self.request.HTTPBodyStream;
}
- (void)setInputStream:(NSInputStream *)inputStream {
NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
mutableRequest.HTTPBodyStream = inputStream;
self.request = mutableRequest;
}
内存管理
在创建AFHTTPRequestOperation
时会将success
和failure
的block
传给operation
,并且在operation
执行完成并回调completionBlock
时,执行这两个block
代码。但是由于completionBlock
中直接使用了self
,导致了循环引用的问题。
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
self.completionBlock = ^{
// something code ...
};
}
completionBlock
的循环引用是AFN
有意而为之的,为的就是保持operation
的生命周期,以保证请求处理完成并接收返回的block
回调。
对于循环引用的生命周期,AFN
采取的是主动打破循环引用的方式,也就是重写父类的completionBlock
,并且在调用block
结束后,主动将completionBlock
赋值为nil
,从而主动打破循环引用。
- (void)setCompletionBlock:(void (^)(void))block {
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"
dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
dispatch_group_async(group, queue, ^{
block();
});
dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
[strongSelf setCompletionBlock:nil];
});
}];
}
}
AFNetworkReachabilityManager
AFNetworking
中还有很重要的一部分,就是Reachability
,用来做网络状态监控的。AFNetworking
、YYKit
、苹果官方都提供有Reachability
的API
使用,内部实现原理基本差不多。
代码实现也很简单,主要依赖SystemConfiguration.framework
框架的SCNetworkReachability
,注册一个Callback
然后等着回调就可以。这里讲一下核心逻辑,一些细枝末节的就忽略了。
Reachability
提供了两种初始化方法,一种是通过域名初始化的managerForDomain:
方法,传入一个域名,基于这个域名的访问情况来判断当前网络状态。另一种是通过地址初始化的managerForAddress:
方法,创建一个sockaddr_in
对象,并基于这个对象来判断网络状态。
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
+ (instancetype)manager {
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
return [self managerForAddress:&address];
}
下面startMonitoring
中是开启网络监测的核心代码,主要逻辑是设置了两个Callback
,一个是block
的一个是函数的,并添加到Runloop
中开始监控。由此可以推测,Reachability
的代码实现主要依赖Runloop
的事件循环,并且在事件循环中判断网络状态。
当网络发生改变时,就会回调AFNetworkReachabilityCallback
函数,回调有三个参数。target
是SCNetworkReachabilityRef
对象,flags
是网络状态,info
是我们设置的block
回调参数。回调Callback
函数后,内部会通过block
以及通知的形式,对外发出回调。
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
AFNetworking总结
AFNetworking
对请求数据的序列化,以及返回数据的反序列化做了很多处理。使开发者只需要传入一个字典即可构建请求参数,无需处理拼接到URL
后面或参数转换为body
二进制的细节,这些都由AFNetworking
内部处理并进行容错,开发者只需要指定请求方式即可。
通过AFNetworking
实现https
也很方便,AFSecurityPolicy
可以很好的管理CA
以及自签名证书,以及处理https
请求过程中的一些逻辑,我们只需要告诉AFNetworking
怎么处理即可,如果不指定处理方式则使用默认CA
证书的方式处理。
AFNetworking
对于后台下载以及断点续传有很好的支持,我们可以在AFNetworking
的基础上,很简单的就完成一个下载模块的设计。如果自己写后台下载和断点续传的代码,工作量还是不小的。
并且AFNetworking
在网络库的设计上还提供了很强的自定义性,例如指定证书、URL
缓存处理,以及下载过程中不同下载阶段的处理。如果没有提供自定义处理,则使用默认处理方式。