iOS_AFNetworking 结构解析+用例分析+源码阅读

在这里插入图片描述
Github OC语言排第一, 其凝聚了众多大神的智慧,无论是在技术点上,还是架构设计上、问题处理方式上,都具有很高的学习价值。

大致结构如下:
在这里插入图片描述

NSURLSession 请求会话

1. AFURLSessionManager (父类)

核心类就是AFURLSessionManager了, 管理所有task、证书验证、网络状态、request和response处理。

Task分为4类,分别对应4个Protocol。根据每个task的属性生成一个AFURLSessionManagerTaskDelegate代理对象存储在mutableTaskDelegatesKeyedByTaskIdentifierdic字典中,key为task的ID,即{taskId-delegate}。并在代理回调中根据taskId取出delegate执行相应的代理方法。

基本实现了NSURLSession的所有代理方法(NSURLSessionDelegateNSURLSessionTaskDelegateNSURLSessionDataDelegateNSURLSessionDownloadDelegate)。

  • 一些代理方法用block包装了出去,下面会介绍。
  • 并把最核心的代理回调交给AFURLSessionManagerTaskDelegate实现。
  • 所有的代理回调都应该在一个串行队列中,这样才能保证代理方法回调的顺序
  • NSOperationQueue: 设置线程最大并发数为 1实现串行,代理回调:异步+串行队列
2. AFHTTPSessionManager (子类)

AFHTTPSessionManager继成自AFURLSessionManager,负责创建Get/Head/Post/Put/Patch/Delete请求,负责管理requestSerializer(请求序化)和responseSerializer(响应序列化)

Serialization 序列化

AFURLRequestSerialization (Protocol)

都遵循AFURLRequestSerialization协议:请求参数序列化

  1. AFHTTPRequestSerializer:构建普通请求: 格式化请求参数, 生成HTTPHeader; 构建multipart请求, 上传数据时会用到
  2. AFJSONRequestSerializer:参数格式是 json
  3. AFPropertyListRequestSerializer:参数格式是苹果的 plist 格式
AFURLResponseSerialization (Protocol)

都遵循AFURLResponseSerialization协议:验证返回数据,反序列化

  1. AFHTTPResponseSerializer:普通的 HTTP 请求,默认数据格式是application/x-www-form-urlencoded,也就是 key-value 形式的 url 编码字符串
  2. AFJSONResponseSerializer:对响应进行JSON解析
  3. AFXMLParserResponseSerializer:对响应进行XML解析
  4. AFXMLDocumentResponseSerializer (macOS):MIME类型,application/xmltext/xml
  5. AFPropertyListResponseSerializer:MIME类型,application/x-plist:苹果的 plist 格式
  6. AFImageResponseSerializer:支持UIImage or NSImage
  7. AFCompoundResponseSerializer:组合器, 可以将多个解析器组合起来, 同时支持多种格式的数据解析
    (具体说明可以看代码里的)

Additional Functionality

  • AFSecurityPolicy
  • AFNetworkReachabilityManager

用例分析

方法里处理的东西,可以下载Demo点进去查看,这里考虑到篇幅的原因,就不贴出来了

1. downloadTask

// 1. 创建configuration(配置)
// NSURLSessionConfiguration 有3个工厂方法
// default: 共享 NSHTTPCookieStorage, NSURLCache, NSURLCredentialStorage
// ephemeral: 不会存储 缓存、Cookie、证书, 适用于秘密浏览
// backgroundWithID: 可以在程序 挂起、退出、崩溃 的情况下, 上传和下载任务, ID用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 2. 创建sessionManager
  // 2.1.创建网络请求回调queue
  // 2.2.创建安全策略
  // 2.3.初始化: 代理字典{task-delegate}、锁(访问代理字典的)
  // 2.4.遍历session中所有的task: 数据task、上传task、下载task
    // 2.4.1 为每个task创建taskDelegate, 并将代理都存入字典中
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 3. 根据URL创建request
NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 4. 创建downloadTask
  // 4.1 运用NSURLSession, 根据request创建downloadTask (系统方法)
  // 4.2 为downloadTask添加taskDelegate
    // 4.2.1 根据downloadTask创建taskDelegate
    // 4.2.2 为taskDelegate设置: manager、competion回调
    // 4.2.3 为taskDelegate设置finish回调
    // 4.2.4 将taskDelegate存入字典中 (加锁)
      // 接收task的暂停和恢复通知 (通过替换系统的`resume`和`suspend`方法, 添加的notify实现)
    // 4.2.5 为taskDelegate设置: progress回调
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request
                                                                 progress:nil
                                                              destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
  // 返回刚下载的文件路径
  NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
                                                                        inDomain:NSUserDomainMask
                                                               appropriateForURL:nil
                                                                          create:NO
                                                                           error:nil];
  return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
  NSLog(@"File downloaded to: %@", filePath);
}];
// 5. 开始执行
[downloadTask resume];

2. uploadTask

// 1. 创建configuration(配置)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 2. 创建sessionManager
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 3. 根据URL创建request
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 4. 获取需要上传文件的URL
NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"photo" ofType:@"PNG"];
NSURL *filePath = [NSURL fileURLWithPath:imageFile];
// 5. 创建uploadTask
  // 5.1 运用NSURLSession根据request和fileURL创建uploadTask (系统方法)
  // 5.2 为uploadTask添加taskDelegate (详情同 downloadTask 4.2)
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request
                                                           fromFile:filePath
                                                           progress:nil
                                                  completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    if (error) {
      NSLog(@"Error: %@", error);
    } else {
      NSLog(@"Success: %@ %@", response, responseObject);
    }
}];
// 6. 开始执行
[uploadTask resume];

3. multiUploadTask

// 1. 根据method、URL、parameters、可变body block、error, 创建mutableRequest
  // 1.1 根据method、URL、parameters、error, 创建mutableRequest
  // 1.2 根据mutableRequest创建formData
  // 1.3 parameters -> [AFQueryStringPair], 并将其添加在formData后面, 有block则调用并传回formData
  // 1.4 设置request
    // 1.4.1 设置bodyStream的初始和结尾边界
    // 1.4.2 将bodyStream作为请求报文体
    // 1.4.3 设置请求头的Content-Type和Content-Length字段
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer]
                                multipartFormRequestWithMethod:@"POST" // 不支持GET, HEAD类型
                                URLString:@"http://example.com/upload"
                                parameters:nil
                                constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
  // formData支持三种格式的数据: NSData, FileURL, NSInputStream
  NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"beautiful" ofType:@"jpg"];
  [formData appendPartWithFileURL:[NSURL fileURLWithPath:imageFile]
                             name:@"file"
                         fileName:@"filename.jpg"
                         mimeType:@"image/jpeg"
                            error:nil];
} error:nil];
// 2. (同downloadTask的1-2)
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
// 3. 运用AFURLSessionManager根据request, 创建streamed uploadTask
  // 详情(同upload 5)
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {
  // 子线程回调, 更新UI需要dispatch到主线程
  dispatch_async(dispatch_get_main_queue(), ^{
    // refresh UI
  });
} completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
  if (error) {
    NSLog(@"Error: %@", error);
  } else {
    NSLog(@"%@ %@", response, responseObject);
  }
}];
// 4. 开始执行
[uploadTask resume];

4. dataTask

// 1. (同downloadTask的1-2)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 2. 根据URL创建request
NSURL *URL = [NSURL URLWithString:@"http://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 3. 运用AFURLSessionManager根据request, 创建dataTask
  // 3.1 运用NSURLSession根据request, 创建dataTask (系统方法)
  // 3.2 为dataTask添加taskDelegate (详情同 downloadTask 4.2)
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
  if (error) {
    NSLog(@"Error: %@", error);
  } else {
    NSLog(@"%@ %@", response, responseObject);
  }
}];
[dataTask resume];

下面是一些巧妙之处:

  • 将网络状态由系统的10个简化到4个,详情见AFNetworkReachabilityStatusForFlags方法
  • 将网络状态改变的通知放到主线程的异步队列中发出,详情见AFPostReachabilityStatusChange方法
dispatch_async(dispatch_get_main_queue(), ^{ //mo: 在主线程队列中异步执行
  // 发出notification
});
  • __Require_noErr_Quiet的使用, 出错跳转到 _out 和 忽略弃用警告宏的使用,详情见AFPublicKeyForCertificate方法
//mo: __Require_noErr_Quiet: 如果出错, 则跳转到 _out
/* 根据证书和政策创建一个信任管理对象
 certificates: 要认证的证书+你认为对证书有用的任何其他证书
 policies: 参考评估政策
 trust: 返回时, 指向新创建的信任管理对象
*/
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
//mo: 忽略弃用警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  //mo: 评估指定 证书 和 策略 的信任 (同步的)
  __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
#pragma clang diagnostic pop
  //mo: 在`叶证书`求值后返回其公钥
  allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
  • 因为可变字典的修改不是线程安全的, 所以添加了NSLock,如管理delegate的mutableTaskDelegatesKeyedByTaskIdentifier字典的读写:
#pragma mark - mo: 代理字典存取
//mo: 字典的操作不是线程安全的, 所以用`NSLock`加锁
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
  NSParameterAssert(task);
  AFURLSessionManagerTaskDelegate *delegate = nil;
  [self.lock lock]; //mo: 读取加锁
  delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
  [self.lock unlock];
  return delegate;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task {
  NSParameterAssert(task);
  NSParameterAssert(delegate);
  [self.lock lock];  //mo: 写入加锁
  self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
  [self addNotificationObserverForTask:task];
  [self.lock unlock];
}
  • 一些属性的String是用NSStringFromSelector生成的 (如: 观察 / 序列化),例如:AFHTTPRequestSerializerObservedKeyPaths获取需要观察的属性字符串

  • KVO的使用
    1.遍历监听自身属性的变化, 将变化的值保存到mutableSet中, 在创建NSMutableURLRequest时设置
    2.当某个属性的getter方法使用其他属性的值计算返回值时, 重写keyPathsForValuesAffectingValueForKey:方法, 返回其他属性的集合,详情见AFHTTPRequestSerializer初始化方法

  • 遍历用的keyEnumerator/objectEnumerator/reverseObjectEnumerator(不可更改的)

//mo: 用`keyEnumerator`遍历keys, 此时不可更改字典 (还有个objectEnumerator可以遍历value)
for (NSString *headerField in headers.keyEnumerator) {
  [request setValue:headers[headerField] forHTTPHeaderField:headerField];
}
  • GCD的使用: 并行队列+同步,实现读写安全
    读: dispatch_sync
    写: dispatch_barrier_sync 保证队列之前的任务都执行完毕, 之后的任务得等自己执行完毕
    AFHTTPRequestSerializer对属性mutableHTTPRequestHeaders的读写:
// 请求头修改队列:并行
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
#pragma mark - request headers 读写
// mo: 并行同步: 读 / 写(barrier: 保证队列之前的任务都执行完毕, 之后得等自己执行完毕)
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
  dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{
    [self.mutableHTTPRequestHeaders setValue:value forKey:field];
  });
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    NSString __block *value;
  dispatch_sync(self.requestHeaderModificationQueue, ^{
    value = [self.mutableHTTPRequestHeaders valueForKey:field];
  });
  return value;
}
  • 初始化方法的处理
    NS_UNAVAILABLE禁用自带初始化函数
    NS_DESIGNATED_INITIALIZER指定初始化函数

  • 将代理方法包装成block, 供外部使用
    重写了respondsToSelector方法, 将判断方法->判断block,
    AFURLSessionManagerrespondsToSelector:方法将判断方法是否实现,改为判断相应的block是否为空,然后在代理方法里调用响应的block。

  • NSSecureCoding而不是NSCoding
    解码方法是: decodeObjectOfClass:而不是decodeObjectForKey:
    因为序列化后的数据可能被篡改, 若不指定Class, decode出来的可能不是原来的对象, 有潜在风险

  • 帮我们组装好了一些HTTP请求头
    AFHTTPRequestSerializer的初始化方法:

  • Content-Type:请求参数类型

  • Accept-Language:根据[NSLocale preferredLanguages]方法读取本地语言,告诉服务端自己能接受的语言。

  • User-Agent:app的boundId/ID/版本, 设备型号/系统/尺寸 等

  • Authorization:提供 Basic Auth 认证接口,帮我们把用户名密码做 base64 编码后放入 HTTP 请求头。

一般我们请求都会按 key=value 的方式带上各种参数,
GET 方法参数直接拼在 URL 后面,POST 方法放在 body 上,
NSURLRequest没有封装好这个参数的序列化,只能我们自己拼好字符串。
AFHTTPRequestSerializer提供了接口,让参数可以是NSDictionaryNSArrayNSSet这些类型,再由内部解析成字符串后赋给NSURLRequest

  • _AFURLSessionTaskSwizzling+loadNSURLSessionTask-resume-suspend替换成自己的,主要是为了添加自己的通知

  • semaphore_t/semaphore_signal/semaphore_wait的使用
    如: 用session的getTask回调获取task时, 运用了semaphore等待block完成后才return,详情见AFURLSessionManager- (NSArray *)tasksForKeyPath:方法

  • NSProgress的使用, 来监听进度, 控制stack的取消,暂停,恢复

  • Block中使用了StrongSelf调用方法

  • 用的 FOUNDATION_EXPORT = extern “C” 指定编译和链接规约, 不影响语义, 只改变编译和链接的方式

  • static修饰const控制常量作用域

  • NSParameterAssert 校验参数完整性

等等,等等。。。
看了一遍,先做一下笔记,以后回顾知新了再更新~
小女子献丑了,文章有哪里不对的,望各位看官指正~

阅读注释用Demo地址

参考文章如下:
AFNetworking(v3.1.0) 源码解析 为何需要使用HTTPs
AFNetworking到底做了什么? 写的非常详细

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫小言mo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值