Demo地址
AFN大体框架
总的思路
url -- request (configureation)-- task --- session -- 代理 --- manager -- client -- 模块网络(RAC)
本章主要分析 NSURLSession
部分
本章对应Demo地址:
NSURLSession流程
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
AFHTTPSessionManager主要构成
Get Post等请求
AFURLSessionManager主要构成
代理请求,回调等任务执行过程
//这里只是初始化,并不是单例,
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
return [[[self class] alloc] initWithBaseURL:nil];
通过configuration 初始化 NSURLSession
调用父类初始化方法, self = [super initWithSessionConfiguration:configuration];
/*
1.初始化一个session
2.给manager的属性设置初始值
*/
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
//设置默认的configuration,配置我们的session
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
//持有configuration
self.sessionConfiguration = configuration;
// 创建session 串行 : 多个task, 回调是异步的
//设置为delegate的操作队列并发的线程数量1,也就是串行队列
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// block 粗糙 数据 :只会返回response,data,error
// delegate : block, 状态码(cancel),进度条等所有的数据。
/*
-如果完成后需要做复杂(耗时)的处理,可以选择异步队列
-如果完成后直接更新UI,可以选择主队列
[NSOperationQueue mainQueue]
*/
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//默认为json解析
self.responseSerializer = [AFJSONResponseSerializer serializer];
//设置默认证书 无条件信任证书https认证
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
//网络状态监听
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 为什么要收集: cancel resume supend : task : id
//delegate= value taskid = key
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
//使用NSLock确保线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
//异步的获取当前session的所有未完成的task。其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有
//后台任务重新回来初始化session,可能就会有先前的任务
//https://github.com/AFNetworking/AFNetworking/issues/3499
//
[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;
}
HTTPS的流程图和详细分析
- 客户端发送请求url,这个url是基于
https
的,https相当于http+ssl加密策略 - 服务端验证这边有
公钥和 私钥
- 服务端通过
公钥 设置加密算法
响应给客户端 - 客户端将
接收到的crt
跟本地的crt
进行比对(本地,后台提供的ca证书或者苹果ca证书
),验证通过后生成一个随机数random key
放到公钥
中加密保存 - 将这个
加密的公钥
发送给 服务端 - 服务端通过
私钥
解开加密的公钥
,获得 客户端的随机数random key
,通过这个key加密
所有要传递的数据,比如response, name,token等 - 将这些数据发送给客户端
- 客户端通过
随机数random key
解密
这就是https的整个流程。
请求参数处理
- (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
{
// http: 请求行 + 请求头 + 请求体
// 多线程 task ?
//返回一个task,然后开始网络请求
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
//开始网络请求
[dataTask resume];
return dataTask;
}
//1.生成request,2.通过request成成task
- (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;
/*
1.先调用AFHTTPRequestSerializer的requestWithMethod函数构建request
2.处理request构建产生的错误 – serializationError
//relativeToURL表示将URLString拼接到baseURL后面
*/
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
这里
[self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]
GET请求头的Content-Type不会设置
POST请求头的Content-Type默认是application/x-www-form-urlencoded
示例中如果设置了POST 请求设置了下面两项,则为application/json
//设置1 manager.requestSerializer = [AFJSONRequestSerializer serializer];
//设置2 [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager GET:urlStr parameters:dic progress:^(NSProgress * _Nonnull downloadProgress) {po request.allHTTPHeaderFields{ "Accept-Language" = "en;q=1"; "User-Agent" = "afn_demo/1.0 (iPhone; iOS 14.4; Scale/3.00)";}
POST的requestSerializer默认是AFHTTPRequestSerializer类,
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
如果设置了下面两项中的其中一项,
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//设置1 manager.requestSerializer = [AFJSONRequestSerializer serializer];
//设置2 [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[manager POST:urlStr parameters:dict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
执行设置1 manager.requestSerializer = [AFJSONRequestSerializer serializer] : 推荐方式,json处理
po self.requestSerializer
<AFJSONRequestSerializer: 0x600003b2b3c0>
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
po parameters
{
username = (
{
age = 18;
name = Cooci;
},
{
age = 19;
name = Gavin;
}
);
}
默认格式就是json
执行设置2 [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"] :
po self.requestSerializer
<AFHTTPRequestSerializer: 0x600000c8c300>
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
query是 username%5B%5D%5Bage%5D=18&username%5B%5D%5Bname%5D=Cooci
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
127.0.0.1:8080的请求头可以使用 WireShakes抓取 本地回环(LoopBack: lo0)的方式
//默认解析方式,dic- count=5&start=1
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//将parameters传入这个c函数
query = AFQueryStringFromParameters(parameters);
break;
}
// 内部方法: C : OC-->C
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];//一对
//把参数传给AFQueryStringPairsFromDictionary,AFQueryStringPair数据处理类
// username = @"haitao"
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
//百分号编码
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//拆分数组返回参数字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
//query:ab%5Bco%5D=sd&ab%5Bhello%5D=123&ie%5B%5D=4&ie%5B%5D=2&ie%5B%5D=3&ie%5B%5D=1&jb=jc&rb=rb&tn=monline_3_dg&wd=%E4%BD%A0%E5%A5%BD
/*
百分号编码count=5*# ASCII uinicode
count=5
*/
- (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])];
}
}
//最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。因为这几个method的query是拼接到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 = @"";
}
//函数会判断request的Content-Type是否设置了,如果没有,就默认设置为application/x-www-form-urlencoded
//application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
//设置请求体
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
复杂数据处理 bug
AFN请求参数百分比编码封装
//使用了递归
/*
接着会对value的类型进行判断,有NSDictionary、NSArray、NSSet类型。不过有人就会问了,在AFQueryStringPairsFromDictionary中给AFQueryStringPairsFromKeyAndValue函数传入的value不是NSDictionary嘛?还要判断那么多类型干啥?对,问得很好,这就是AFQueryStringPairsFromKeyAndValue的核心----递归调用并解析,你不能保证NSDictionary的value中存放的是一个NSArray、NSSet。
*/
//第二次,key=count valus=5
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {//key=count value = 5
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
/*
根据需要排列的对象的description来进行升序排列,并且selector使用的是compare:
因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数
*/
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
/*
allkeys:count/start
*/
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];//nestedkey=count nestedvalue=5
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:@"%@[%lu]", key,(unsigned long)[array indexOfObject:nestedValue]], nestedValue)];
[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 {//既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value是一个NSString,那么就得调用函数中最后的else语句,
//AFQueryStringPair数据处理类,mutableQueryStringComponents中的元素类型是AFQueryStringPair
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
AFN中 Value为数组的时候,就会存在问题,
NSString *urlStr = @"http://127.0.0.1:8080/postMethod/array";
NSDictionary *dict = @{@"username":
@[
@{
@"name":@"Cooci",
@"age":@18
},
@{
@"name":@"Gavin",
@"age":@19
}
]
};
此时元素的key是相同的
ie%5B%5D=4&ie%5B%5D=2&ie%5B%5D=3&ie%5B%5D=1
解决方案:
1.修改第三方的代码,手动添加数组的索引,不建议
//数据没有索引问题。
//[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%lu]", key,(unsigned long)[array indexOfObject:nestedValue]], nestedValue)];
2.将数组转成json字符串,不会有数组索引的问题
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&parseError];
NSString *jsonstr =[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
jsonstr = [jsonstr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
jsonstr = [jsonstr stringByReplacingOccurrencesOfString:@" " withString:@""];
jsonstr = [jsonstr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
NSDictionary *dict2 = @{@"username":jsonstr};
task与delegate
通过上面的步骤,我们已经初始化NSURLSession和UIRequest了。 接下来就是通过request初始化task
// 请求行 + 请求头 + 请求体
// 多线程 task ?
//返回一个task,然后开始网络请求
同时多个重复请求进来的时候,保证回调的时候不会错乱, 同步串行,第一个请求没完成之前,第二个请求不能执行。
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
//原生的方法
//使用session来创建一个NSURLSessionDataTask对象
dataTask = [self.session dataTaskWithRequest:request];
});
//task和block不匹配
//taskid应该是唯一的,并发的创建的task,id不唯一
static void url_session_manager_create_task_safely(dispatch_block_t block) {
NSLog(@"NSFoundationVersionNumber = %f",NSFoundationVersionNumber);
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
dispatch_sync(url_session_manager_creation_queue(), block);//同步
} else {
block();
}
}
核心代码
// 初始化delegate
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
//函数字面意思是将一个session task和一个AFURLSessionManagerTaskDelegate类型的delegate变量绑在一起,而这个绑在一起的工作是由我们的AFURLSessionManager所做。至于绑定的过程,就是以该session task的taskIdentifier为key,delegate为value,赋值给mutableTaskDelegatesKeyedByTaskIdentifier这个NSMutableDictionary类型的变量。知道了这两者是关联在一起的话,马上就会产生另外的问题 —— 为什么要关联以及怎么关联在一起?
[self setDelegate:delegate forTask:dataTask];
//为task设置关联的delegate
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
//task和delegate都不能为空
NSParameterAssert(task);
NSParameterAssert(delegate);
//加锁确保中间代码块是原子操作,线程安全
[self.lock lock];
//将delegate存入字典,以taskid作为key,说明每个task都有各自的代理
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
//设置两个NSProgress的变量 - uploadProgress和downloadProgress,给session task添加了两个KVO事件
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
delegate持有AFHTTPSessionManager和外部的请求回调
task持有delegate
//将delegate存入字典,以taskid作为key,说明每个task都有各自的代理
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
//weak防止循环引用(manager持有task,task和delegate是绑定的,相当于manager是持有delegate的)
@property (nonatomic, weak) AFURLSessionManager *manager;
打破循环引用recycle
task与manager.
这块自行查看Demo, 主要讲解 NSURLSessionTaskDelegate 回调的问题
//回调方法,收到数据
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
//拼接数据
// 数据流 内存?
NSLog(@"delegate--%@",[NSThread currentThread]);
//task.id = deleagte
// 请求home 没完 VS 请求detail? 数据会不会混乱 : 不会
// deleaget.mutableData --> task.id
// managr --> session : task
[self.mutableData appendData:data];
}
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error{
/Performance Improvement from #2672
//具体可以查看#issue 2672。这里主要是针对大文件的时候,性能提升会很明显
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
接收到数据后,拼接,didCompleteWithError执行完成,如果数据存在,保存一份临时变量返回出去,同时将mutableData设置为空,属性就不会一直增大。
// 1:home : task task根据url创建的msmutablerequest来创建,dataTask = [self.session dataTaskWithRequest:request];
// 2: 创建临时变量 : task.id = delegate
// detail : task
//2: 创建另外临时变量 task.id = delegate
//将delegate存入字典,以taskid作为key,说明每个task都有各自的代理
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
self.mutableData此时的self是delegate。delegate是不同的,所以不会错乱。
附上 Xcode配置Python环境: blog.csdn.net/qq_15289761…