前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >iOS_AFNetworking 结构解析+用例分析+源码阅读

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

作者头像
mikimo
发布2022-07-20 14:18:14
发布2022-07-20 14:18:14
59400
代码可运行
举报
文章被收录于专栏:iOS开发~iOS开发~
运行总次数:0
代码可运行

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

代码语言:javascript
代码运行次数:0
运行
复制
// 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

代码语言:javascript
代码运行次数:0
运行
复制
// 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

代码语言:javascript
代码运行次数:0
运行
复制
// 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

代码语言:javascript
代码运行次数:0
运行
复制
// 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方法
代码语言:javascript
代码运行次数:0
运行
复制
dispatch_async(dispatch_get_main_queue(), ^{ //mo: 在主线程队列中异步执行
  // 发出notification
});
  • __Require_noErr_Quiet的使用, 出错跳转到 _out 和 忽略弃用警告宏的使用,详情见AFPublicKeyForCertificate方法
代码语言:javascript
代码运行次数:0
运行
复制
//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字典的读写:
代码语言:javascript
代码运行次数:0
运行
复制
#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(不可更改的)
代码语言:javascript
代码运行次数:0
运行
复制
//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的读写:
代码语言:javascript
代码运行次数:0
运行
复制
// 请求头修改队列:并行
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到底做了什么? 写的非常详细

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NSURLSession 请求会话
    • 1. AFURLSessionManager (父类)
    • 2. AFHTTPSessionManager (子类)
  • Serialization 序列化
    • AFURLRequestSerialization (Protocol)
    • AFURLResponseSerialization (Protocol)
  • Additional Functionality
  • 用例分析
    • 1. downloadTask
    • 2. uploadTask
    • 3. multiUploadTask
    • 4. dataTask
  • 下面是一些巧妙之处:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档