前言
学习一下大神的编码思想是很有必要的,另外用了怎么久的开源库不认真对待有点说不过去。这里借助部分博客以及源码来学习。如有不足,多多指点!
目录
- AFNetworking架构及主要模块的功能
- AFNetworking具体模块的探究
AFNetworking架构及主要模块的功能
AFNetworking的导入这里就不多介绍,直接放目录
可以清楚的看到AFNetworking大致分为以下几个模块
- 网络通信模块
- AFHTTPSessionManager
- AFURLSessionManager
- 网络状态监听模块
- AFNetworkReachabilityManager
- 网络通信安全策略模块(有关https的部分)
- AFSecurityPolicy
- 网络通信信息序列化/反序列化模块
- AFURLRequestSerialization
- AFURLResponseSerialization
- 对于iOS UIKit库的扩展(UIKit)
- UIKit
UIKit与其他的没有直接关系。除去后
其中AFHTTPSessionManager是继承于AFURLSessionManager的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager或者其它类去做。
首先写一个GET请求:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];
[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
首先我们用初始化方法生成一个manager,我们点进去看下初始化做了什么:
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL+URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
- 初始化都调用了
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
- 其实初始化方法都调用了父类的初始化方法,父类也就是AF3.x最最重要的AFURLSessionManager。几乎所有的类都是围绕着这个类在处理业务逻辑。
- 除此之外,方法中把baseURL存了起来,还生成了一个请求序列对象和一个响应序列对象。后面再细说这两个类是干嘛的。
直接来到父类AFURLSessionManager的初始化方法:
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
//初始化session,设置代理
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//响应转码
self.responseSerializer = [AFJSONResponseSerializer serializer];
//设置默认安全策略
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典(重点,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
//设置AFURLSessionManagerTaskDelegate 词典的🔒,确保词典在多线程访问的线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
//置空task关联的代理
[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];
}
}];
return self;
}
- 这个就是最终的初始化方法了,注释应该写的很清楚,唯一需要说的就是三点:
-
self.operationQueue.maxConcurrentOperationCount = 1;
这个operationQueue就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了。 - 第二就是我们初始化了一些属性,其中包括
self.mutableTaskDelegatesKeyedByTaskIdentifier
,这个是用来让每一个请求task和我们自定义的AF自定义代理来建立映射用的,其实对task的代理运行了一个封装,并且转发代理到AF代理来建立映射用,其实AF对task的代理进行了一个封装,并且转发代理到AF自定义的代理,这是AF比较重要的一部分,接下来我们会具体讲这一块。 - 第三就是下面这个方法:
-
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
}];
首先说说这个方法是干什么的:这个方法用来异步的获取当前session的所有未完成task。其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有。我们打断点 去看看,也确实如此,里面的数组都是空的。
- **原来这是无力从后台回来,重新初始化session,防止一些之前的后台请求任务,导致程序crash。
初始化方法到这里就完成了。
接下来我们看看网络请求:
- (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;
}
方法走到类AFHTTPSessionManager中来,调用父类,也就是我们整个AF3.x的核心类AFURLSessionManager的方法,生成了一个系统的NSURLSessionDataTask实例,并且开始网络请求。
我们继续往父类里看,看看这个方法到底做了什么:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
- 这个方法做了两件事:
1、用self.requestSerializer和各种参数去获取了一个我们最终网络需要的NSMutableURLRequest实例。
2、调用另外一个dataTaskWithRequest去拿到我们最终需要的NSURLSessionDataTask实例,并且在完成的回调里,调用我们传过来的成功和失败的回调。 - 注意下面这个方法,我们常用来push pop 搭配,来忽略一些编译器的警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
这里是用来忽略:?带来的警告,具体的各种编辑器警告描述,可以参考这篇:各种编译器的警告
- 说到底这个方法还是没有做实事,我们继续到requestSerializer方法里去看,看看AF到底如何拼接我们需要的request的:
接着我们跑到AFURLRequestSerialization类中:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
//断言,debug模式下,如果缺少改参数,crash
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
//将request的各种属性循环遍历
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
//如果自己观察到的发生变化的属性,在这些方法里
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
//把给自己设置的属性给request设置
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//将传入的parameters进行编码,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
- 讲一下这个方法,这个方法做了3件事:
1)设置request的请求类型,get,post,put、、、等
2)往request里添加一些参数设置,其中AFHTTPRequestSerializerObservedKeyPaths()是个C函数,返回一个数组,我们来看看这个函数:
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
// 此处需要observer的keypath为allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies
// HTTPShouldUsePipelining、networkServiceType、timeoutInterval
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
//就是一个数组里装了很多方法的名字,
return _AFHTTPRequestSerializerObservedKeyPaths;
}
其实这个函数就是封装了一些属性的名字,这些都是NSUrlRequest的属性。
再来看看self.mutableObservedChangedKeyPaths,这个是当前类的一个属性:
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
在-init方法对这个集合进行了初始化,并且对当前类的和NSUrlRequest相关的那写属性添加了KVO监听:
//每次都会重置变化:
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
//给这些方法添加观察者为自己,就是request的各种属性,set方法
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
KVO触发的方法:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
//当观察到这些set方法被调用了,而且不为null就会添加到集合中,否则移除
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
至此我们知道self.mutableObservedChangedKeyPaths
其实就是我们自己设置的request属性值的集合。
接下来调用:
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
用KVC的方式,把属性值都设置到我们请求的request中去。
3)把需要传递的参数进行编码,并且设置到request中去:
//将传入的parameters进行编码,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
#pragma mark - AFURLRequestSerialization
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//到自己的header中遍历,如果有值则设置给request的header
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
//来把各种类型的参数,array,dic,set转化成字符串,给request
NSString *query = nil;
if (parameters) {
//自定义解析方式
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
//AF默认解析方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
这个方法做了3件事:
1、从 self.HTTPRequestHeaders
中拿到设置的参数,赋值要请求的request里去
2、把请求网络的参数,从array dic set 这些容器类型转换为字符串,具体转码方式可以使用自定义,也可以用AF默认的转码方式。自定义的方式没什么的由自己决定。下面看看默认的方式:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
//把参数给AFQueryStringPairsFromDictionary,拿到AF的一个类型的数据就是一个key,value对象,在URLEncodedStringValue拼接keyValue,一个加到数组里
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//拆分数组返回参数字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
//往下回调
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
//根据需要排列的对象的descript来进行升序排列,并且selector使用的是compare:
//因为对象的description返回的是NSString,所以此处的compare:使用的是NSString的compare函数
//即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
//判断value是什么类型,然后去递归调用自己,知道解析的除了array,dic,set以外的元素,然后把得到的参数数组返回。
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
//拿到
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;
}
- 转码主要是以上三个函数,配合着注释应该也很好理解:主要是在递归调用
AFQueryStringPairsFromKeyAndValue
,判断value是什么类型,然后去递归调用自己,知道解析的是除了array、dic、set以外的元素,然后把得到了参数数组返回。- 其中有个
AFQueryStringPair
对象,其中只有两个属性和两个方法:
- 其中有个
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation AFQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
- (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])];
}
}
以上方法举个例子梳理下:
@{
@"name" : @"bang",
@"phone": @{@"mobile": @"xx", @"home": @"xx"},
@"families": @[@"father", @"mother"],
@"nums": [NSSet setWithObjects:@"1", @"2", nil]
}
->
@[
field: @"name", value: @"bang",
field: @"phone[mobile]", value: @"xx",
field: @"phone[home]", value: @"xx",
field: @"families[]", value: @"father",
field: @"families[]", value: @"mother",
field: @"nums", value: @"1",
field: @"nums", value: @"2",
]
->
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
至此,我们原来的容器类型的参数,就这样变成了字符串类型了。
紧接着这个方法还根据request中的请求类型,来判断参数字符串应该如何设置到request中去,如果是GET、HEAD、DELETE,则把参数quey拼接到url后面,而POST、PUT把query拼接到http body中:
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
至此,我们生成了一个request。
我们再回到AFHTTPSessionManager类中来,回到这个方法: