阅读 789

AFN源码解析 | 第一天

Demo地址

github.com/tanghaitao/…

AFN大体框架

image.png

总的思路

url -- request (configureation)-- task --- session -- 代理 --- manager -- client -- 模块网络(RAC)

本章主要分析 NSURLSession部分

本章对应Demo地址:

github.com/tanghaitao/…

NSURLSession流程

image.png

@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>

AFHTTPSessionManager主要构成

Get Post等请求 image.png

AFURLSessionManager主要构成

代理请求,回调等任务执行过程

image.png

//这里只是初始化,并不是单例,
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的流程图和详细分析

image.png

  1. 客户端发送请求url,这个url是基于https的,https相当于http+ssl加密策略
  2. 服务端验证这边有 公钥和 私钥
  3. 服务端通过 公钥 设置加密算法 响应给客户端
  4. 客户端将 接收到的crt本地的crt进行比对(本地,后台提供的ca证书或者苹果ca证书),验证通过后生成一个随机数random key 放到 公钥 中加密保存
  5. 将这个 加密的公钥 发送给 服务端
  6. 服务端通过 私钥 解开 加密的公钥,获得 客户端的 随机数random key,通过这个key 加密 所有要传递的数据,比如response, name,token等
  7. 将这些数据发送给客户端
  8. 客户端通过 随机数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];
复制代码

image.png

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]];
}
复制代码

image.png

复杂数据处理 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
                              }
                           ]
                        };
复制代码

image.png

image.png

此时元素的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和外部的请求回调

image.png

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…

文章分类
iOS
文章标签