大概分析一下AFNetworking功能和使用流程。
使用苹果的 NSURLSession 发起一个 POST 请求大概是以下这种方式:
// 创建NSMutableURLRequest对象,给它设置请求头、请求体
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:URL]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[@"pram1=p1" dataUsingEncoding:NSUTF8StringEncoding]];
// 创建NSURLSession对象,设置配置信息、代理和代理回调线程
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
// 使用session去生成一个网络请求任务NSURLSessionDataTask,通过 block 或代理回调结果
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSLog(@"%@",dict);
}];
// 开启这个请求任务
[task resume];
AFNetworking 的请求方式:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:URL parameters:@{@"pram1":@"p1"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@",responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
创建好AFHTTPSessionManager
对象之后,直接进行 GET/POST 等请求。请求和响应都被封装了起来。
AFNetworking的封装
请求封装:
AFURLRequestSerialization
负责封装请求参数,通过传入的URL、请求类型、请求参数创建一个request ,并设置请求头,一般设置以下几个请求头参数:
- Accept-Language:
告诉服务器本地支持的语言类型。一般格式如下:
Accept-Language zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3,zh-cn是简体中文,zh是中文,q的值越大表示越倾向该语言。 - User-Agent:
用户代理,简称UA。它里边包含了一些本地信息,如浏览器类型、操作系统及版本、CPU 类型等,可自行添加其它需要的信息,如网络环境。 - Authorization :
这个是可选项,如果访问的服务器是通过Basic Auth进行身份验证的,可以把账号密码通过它添加进请求头中。一般都使用https,因为http直接暴露了账号密码。 - Content-Type:
常用的 Content-Type 有 以下几种:
text/plain; charset=utf-8,表示请求体是纯文本格式,utf-8编码;
application/json; charset=utf-8,表示请求体是JSON字符串,如{"key":"value"};
application/x-www-form-urlencoded; charset=utf-8,表示请求体是键值对格式,如"key"="value",这是AFNetworking默认的请求体格式;
multipart/form-data; charset=utf-8; boundary=Boundary_XX,表示上传文件,一般上传的文件都比较大,需要分割,boundary表示分割符。
AF中共有三个类负责创建 request :
AFHTTPRequestSerializer
、
AFJSONRequestSerializer
、
AFPropertyListRequestSerializer
。
1、默认是通过 AFHTTPRequestSerializer 创建,它会把通过字典传入的参数转换为键值对的形式 key1=value1&key2=value2,key 和 value 都是经过编码的。
如果是 GET 请求,参数会通过 ?直接拼接在 URL 后面,如:http://localhost?key1=value1&key2=value2。
如果 POST 请求,AF 会把 Content-Type 设置为 application/x-www-form-urlencoded; charset=utf-8,然后把参数通过 UTF-8 编码后放入请求体中。
2、若通过 AFJSONRequestSerializer 创建,GET 请求与上面相同,POST 请求时,AF 会把 Content-Type 设置为 application/json; charset=utf-8,参数直接使用NSJSONSerialization
进行序列化,然后放入请求体中。
3、若通过 AFPropertyListRequestSerializer 创建,Content-Type 设置为 application/x-plist,参数通过NSPropertyListSerialization
序列化后放入请求体。
开发中用的最多的是前两种序列化方式,第三种我还从来没用过。
上传文件:
上传文件有严格的格式要求:
(开头分割线 "--" 加 "boundary" ,此处的boundary是Boundaryxxx)
--Boundaryxxx
(文件参数)
Content-Disposition: form-data; name="file"; filename="picture.png"
(文件类型)
Content-Type: image/png
(文件二进制数据)
...png datas...
(结束分割线 "--" 加 "boundary" 加 "--")
--Boundaryxxx--
其中第二步的 form-data 就像 application/json 一样表示上传格式,name 是接口参数,比如服务器要求上传文件的参数名为 "abc",就要设置 name="abc",filename 是文件的名字。
若有多个文件,重复前四步即可。如前面所说,上传文件要给request设置请求头 Content-Type 为 multipart/form-data; boundary=Boundaryxxx。boundary 就是分割数据时设置的标示。
AF 提供了几个不同的上传方法。
(具体的文件读取有些麻烦,以后再写)
未完待续。。。
响应封装:
AFURLResponseSerialization
类负责响应数据处理。在 AFURLSessionManager 中创建好 dataTask 后会为这个 dataTask 设置代理AFURLSessionManagerTaskDelegate
,每个 AFURLSessionManager 都有一个代理。
NSURLSession 可以添加大量的 NSURLSessionTask 在后台下载,想要检查某个任务进度,只需 double taskProgress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;
就能计算出来。
dataTask 的数据接收和接收完毕代理方法都会被转接到代理类中,代理对象会创建观察者监听 dataTask (NSURLSessionTask对象) 的几个属性,当发送/接收到数据时、任务状态改变时,这些数据会发生变化,这样就能监听到 上传/下载进度 和 取消/开启任务。
请求完毕后使用responseSerializer
开始对结果进行解析,AF 默认使用 AFJSONResponseSerializer 类解析,它的默认可解析类型有:@"application/json", @"text/json", @"text/javascript"。
一般情况下我们的服务器返回的都是 JSON 数据,不过有时候也会返回一些其他类型的数据,导致 AF 不能被解析而返回错误,这就有了网上的一些解决办法:
AFHTTPSessionManager *manager =[AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html",@"text/plain", nil];
十分地简单粗暴😆。
除了 AFJSONResponseSerializer,还有一些其它解析类:
AFHTTPResponseSerializer,直接返回 NSData 数据;
AFXMLParserResponseSerializer,使用 NSXMLParser 进行解析
AFPropertyListResponseSerializer,使用 NSPropertyListSerialization 进行解析;
AFImageResponseSerializer,解析成 Image。
解析完成后,通过completionHandler
回调结果。
平时最常用的 GET/POST 请求流程说完了,说下其它功能吧。
网络监听:
AFNetworkReachabilityManager 类用来监听网络变化,可以单独拉出来使用。它的原理跟苹果官方的 Reachability 相同,都是用 SCNetworkReachability 来进行监听的。
不过 Reachability 并没有那么强大,苹果解释说它并不能真正检测到程序是否可以连接到服务器,只能检测到接口是否可连接,以及接口的网络类型。
也就是说,程序只连接到路由器,而路由器没有连接到外网,是检测不出来的;网络信号差,无法真正和服务器连接通信,也检测不出来。网上有人给出一种解决方案:发送一个真实的网络请求,或者使用socket编程,建立一个tcp链接来检测(三次握手成功),只要链接成功则服务器可达。这样只会发送tcpip的报头,数据量最小。如果网络环境差,connect函数会阻塞,所以最好不要在主线程下,调用示例代码:
#import <arpa/inet.h>
/// 服务器可达返回true
- (BOOL)socketReachabilityTest {
// 客户端 AF_INET:ipv4 SOCK_STREAM:TCP链接
int socketNumber = socket(AF_INET, SOCK_STREAM, 0);
// 配置服务器端套接字
struct sockaddr_in serverAddress;
// 设置服务器ipv4
serverAddress.sin_family = AF_INET;
// 百度的ip
serverAddress.sin_addr.s_addr = inet_addr("202.108.22.5");
// 设置端口号,HTTP默认80端口
serverAddress.sin_port = htons(80);
if (connect(socketNumber, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)) == 0) {
close(socketNumber);
return true;
}
close(socketNumber);;
return false;
}
GitHub上也有检测网络真实环境的轮子如RealReachability。
未完待续。。。
网友评论