共计 39798 个字符,预计需要花费 100 分钟才能阅读完成。
该文章属于 < 简书 — 刘小壮 > 原创,转载请注明:
< 简书 — 刘小壮 >
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
缓存处理,以及下载过程中不同下载阶段的处理。如果没有提供自定义处理,则使用默认处理方式。