AFNetworking源码简析
AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOS和OS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人员的喜爱。
本文主要介绍一下AFNetworking(版本:3.1.0)的模块结构、请求的执行过程、网络状态监测以及网络安全的处理等等,从而对AFNetworking的具体功能、执行过程有一个大致的了解,在实际的项目开发过程中,能够更好的进行应用。
一、结构
下面是AFNetworking的源码结构图,主要分为:NSURLSession核心代码、Reachability、Security、Serialization和UIKit等5部分。
AFNetworking的源码结构图:

AFHTTPSessionManager的依赖关系:

AFNetworking的网络数据的序列化(Serialization)的类结构图:

NSURLSessionTask的类结构图:

各部分功能介绍如下:
1.NSURLSession
这是网络请求的核心代码,承担发送请求、接收数据、异常处理等功能,以及请求和响应的数据序列化功能
2.Reachability
用来监控设备的网络状态,只能识别WWAN和WiFi这两种网络
3.Security
网络安全的设计,尤其是利用https增强数据安全性
4.Serialization
请求和响应数据的序列化处理
5.UIKit
主要是对一些常用UI控件做网络交互方便的扩展
二、应用
1.网络状态监测
网络状态监测,通过AFNetworkReachabilityManager的单例对象设置网络状态变化的block,然后通过AFStringFromNetworkReachabilityStatus把当前网络状态转换为对应的字符串。一般情况下,设置网络监测的代码放到AppDelegate.m里,监测整个项目的网络状态变化。
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
2.网络安全
HTTPS相比HTTP提高了通信的安全性,而苹果也建议网络请求使用HTTPS,所以有必要了解一下HTTPS的工作过程(HTTPS通信流程介绍)。而AFNetworking中的
AFSecurityPolicy类用来验证证书是否有效,从而防止被中间人攻击。在实际开发中,会在AFURLSessionManager类的初始化方法里使用默认的安全设置:
- 不允许无效或过期的证书
- 验证domain名称
- 不对证书和公钥进行验证
// AFURLSessionManager.m
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
...
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
...
}
// AFSecurityPolicy.m
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
部分重要属性介绍如下:
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
a、AFSSLPinningModeNone:不验证服务器的整数,完全信任
b、AFSSLPinningModePublicKey:验证服务器返回的证书中的PublicKey
c、AFSSLPinningModeCertificate:把服务器的返回的证书和本地证书进行验证
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
这个属性保存着所有的可用做校验的证书的集合
@property (nonatomic, assign) BOOL allowInvalidCertificates;
是否允许无效或过期的证书,默认是不允许
@property (nonatomic, assign) BOOL validatesDomainName;
是否验证证书中的域名domain,默认是验证
3.HTTP请求
HTTP请求在开发中常用的就是GET、POST这两种类型,针对这两种请求方式的区别请参考HTTP协议格式详解。本文从源码实现的角度,不会详细介绍各个类的功能及依赖关系,只是面向应用的角度来分析一下这两种请求如何实现的:
整个网络协议的流程图:

GET/POST
AFNetworking发起的网络请求,GET和POST的差异就是在设置请求的URL和HTTPBody时的差异。下面根据上面的流程图详细介绍一下具体的代码实现:
步骤一:发起网络请求
- 通过
requestSerializer.timeoutInterval设置请求超时时间 - 通过
responseSerializer.acceptableContentTypes设置客户端可接收的数据类型
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 10;
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", nil];
[manager GET:@"https://app.chaoaicai.com/api/todayApi/discoveryInfo.app" parameters:@{@"name": @"kelvin"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
步骤二、三:创建NSMutableURLRequest 和 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
{
...
// 创建Request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
...
// 创建dataTask
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
...
}];
...
}
步骤四:设置AFURLSessionManager的代理
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
// 创建dataTask
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
// 设置dataTask的代理
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
步骤五:调用NSURLSessionDataTask的resume方法开始网络请求
通过调用NSURLSessionTask的resume来启动任务,也可以调用suspend来挂起任务
- (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
{
...
[dataTask resume];
...
}
步骤六:AFURLSessionManagerTaskDelegat的方法处理回调数据
这个方法里主要功能是处理接收到的数据,调用保存的任务完成的block,并发送接收完成的通知
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
...
if(error){
...
} else {
...
}
}
步骤7:清理NSURLSessionDataTask配置
删除NSURLSessionDataTask的配置信息,如:删除监控收发数据进度的通知、任务开启或挂起的通知、从mutableTaskDelegatesKeyedByTaskIdentifier中删除任务的delegate等等。之后,回调用户设置的block(成功或者失败的回调),等block执行结束后,就结束这个GET请求。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
// 调用代理方法处理接收到的数据
[delegate URLSession:session task:task didCompleteWithError:error];
// 清理dataTask的配置信息
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
// 回调用户的block
self.taskDidComplete(session, task, error);
}
}
// 清理dataTask配置
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
[self.lock lock];
[delegate cleanUpProgressForTask:task];
[self removeNotificationObserverForTask:task];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}
下面介绍一下GET和POST在请求时,封装URL和HTTPBody的差别:
- GET请求
把parameters字典转换为key=value&key=value的形式,并附加在用户设置的url的后面作为请求的url
- POST请求
把parameters字典转换为key=value&key=value的形式,然后添加到HTTPBody里面作为请求体发送
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 添加用户配置的header的key-value值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
...
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
// GET 请求
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
// POST 请求
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;
}
4.普通请求(直接用NSURLSession)
AFNetworking除了直接用GET或POST发送请求外,还可以直接用AFURLSessionManager发送网络请求,而GET或POST内部其实也是调用的AFURLSessionManager,这样我们也可以手动设置任务的开启或挂起。
下面是AFNetworking的Github的示例代码,关于上传或下载代码,请参考AFNetworking的介绍
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://fishbay.cn"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
至此,AFNetworking的分析到此结束,本文只是从应用的角度分析了一下源码的实现,如有不足之处,欢迎指正。(本文部分图片来自互联网,版权归原作者所有)
参考资料
AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy
AFNetworking源码简析的更多相关文章
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...
- django-jwt token校验源码简析
一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...
- 0002 - Spring MVC 拦截器源码简析:拦截器加载与执行
1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日 ...
- OpenStack之Glance源码简析
Glance简介 OpenStack镜像服务器是一套虚拟机镜像发现.注册.检索. glance架构图: Glance源码结构: glance/api:主要负责接收响应镜像管理命令的Restful请求, ...
- ElementUI 源码简析——源码结构篇
ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的.作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构. ...
- DRF之APIView源码简析
一. 安装djangorestframework 安装的方式有以下三种,注意,模块就叫djangorestframework. 方式一:pip3 install djangorestframework ...
- spring ioc源码简析
ClassPathXmlApplicationContext 首先我们先从平时启动spring常用的ClassPathXmlApplicationContext开始解析 ApplicationCont ...
- 并发系列(二)——FutureTask类源码简析
背景 本文基于JDK 11,主要介绍FutureTask类中的run().get()和cancel() 方法,没有过多解析相应interface中的注释,但阅读源码时建议先阅读注释,明白方法的主要的功 ...
随机推荐
- TCP 连接重置漏洞 - CVE-2004-0230讲解
TCP 连接重置漏洞 - CVE-2004-0230: IPv6 实施中存在一个拒绝服务漏洞,该漏洞可能允许攻击者向受影响系统发送特制的 TCP 消息. 成功利用此漏洞的攻击者可能会导致受影响系统重置 ...
- jQuery选择器分类
jQuery的基本选择器 选择器的分类 <!--1.基本选择器 2.层级选择器 3.过滤选择器 3.1 基本过滤选择器 3.2 内容过滤选择器 3.3 可见性过滤选择器 3.4 子元素过滤选择器 ...
- 第一章:selenium + java 环境安装
1. 首先安装的是 jdk ,推荐安装的是 JDK 版本是 1.7 2. 安装 JDK 步骤: 第一步: 第二步: 第三步: 配置 JDK 环境变量,点击 我的电脑-右键-高级-环境变量. 第五 ...
- java 对象与json互转
有时为了项目需求,会将对象数据转换成json数据,以下是个人根据项目需求实现的方法. 项目中需要将数据格式: [{ "node": "0", "ind ...
- mysql5.7 date类型无法设置'0000-00-00'默认值
现象: mysql5.7之后版本datetime默认值设置'0000-00-00',出现异常:Invalid default value for 'create_time' 原因: mysql5.7之 ...
- 谈谈HashMap与HashTable
谈谈HashMap与HashTable HashMap 我们一直知道HashMap是非线程安全的,HashTable是线程安全的,可这是为什么呢?先聊聊HashMap吧,想要了解它为什么是非线程安全的 ...
- java基础04 匿名内部类
内部类 (1)把类定义在另一个类的内部,该类就被称为内部类. 举例:把类B定义在类A中,类B就被称为内部类. (2)内部类的访问规则 A:可以直接访问外部类的成员,包括私有 B:外部类要想访问内部类成 ...
- (转)每天一个linux命令(50):crontab命令
场景:在学习Linux环境下自动部署项目时候,可以通过crontab命令设定定时任务,实现服务端项目的自动部署! 前一天学习了 at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系 ...
- 【流量】netflow 基础知识
摘要 记录下关于netflow的基础知识以及应用,现状 是什么 一种数据交换方式,NetFlow流量统计数据包括数据流时戳 源IP地址和目的IP地址 源端口号和目的端口号 输入接口号和输出接口号 下一 ...
- PowerShell 脚本中的密码
引言 笔者在<PowerShell 远程执行任务>一文中提到了在脚本中使用用户名和密码的基本方式: $Username = 'xxxx' $Password = 'yyyy' $Pass ...