为了迎合iOS新版本的升级, AFNetworking在3.0版本中删除了基于 NSURLConnection API的所有支持。如果你的项目以前使用过这些API,建议您立即升级到基于 NSURLSession 的API的AFNetworking的版本。AFNetworking 3.0正式支持的iOS 7, Mac OS X的10.9, watchOS 2 , tvOS 9 和Xcode 7。本文将从以下几点说明:
首先说明AFNetworking的集成过程
1、从github下载AFNetworking3.0,把AFNetworking.h 和 UIKit+AFNetworking.h导入工程
(下载地址:https://github.com/AFNetworking/AFNetworking.git)
注意!!在AFNetworking中是不支持text/html的
遇到请求头到错误时在AFURLResponseSerialization.m里修改
1 //把@"text/html" 添加到下面代码里就行,
2 self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html",@"text/plain", nil];
或者在需要的地方写代码
1 //设置请求头contenttypes信息
2 manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];
2、简单的使用封装
AFNetworking的使用主要分为三步:
(1) 创建会话(Session)
(2) 创建请求(request)
(3) 创建任务(task),对数据进行上传。
//初始化
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
如果后台接口是二进制格式
//默认的
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
如果后台接口是JSON格式
manager.requestSerializer = [AFJSONRequestSerializer serializer]; manager.responseSerializer = [AFJSONResponseSerializer serializer];
GET
Request
[manager GET:@"example" parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
//这里可以用来显示下载进度
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//成功
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//失败
}];
POST
Request
//post请求参数一般用字典,键要与请求url的参数名一致。
[manager POST:@"example" parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
//进度
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//成功
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//失败
}];
Shared Network Reachability(网络状态的监控)
//监听网络 /* AFNetworkReachabilityStatusUnknown = -1, AFNetworkReachabilityStatusNotReachable = 0, AFNetworkReachabilityStatusReachableViaWWAN = 1, AFNetworkReachabilityStatusReachableViaWiFi = 2, NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case AFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } } */ [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status)) }];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
//单纯判断有无网络可以用isReachable属性 if ([[AFNetworkReachabilityManager manager] isReachable] == NO) { //do something }
上传图片
图片上传的时候要问后台只能单张上传或者多张上传.
//单张上传 [manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { //创建待上传图片的名称,为了不重名,一般使用当前时间+图片类型(eg .png)为名称 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyyMMddHHmmss"; //服务器保存上传图片的路径,这个路径后台给的话用后台的,没给就自己创建(此处没给) NSString *name = [formatter stringFromDate:[NSDate date]]; //后缀 .png .jpg NSString *fileName = [[formatter stringFromDate:[NSDate date]] stringByAppendingString:@".jpg"]; /* *UIImageJPEGRepresentation(image,1.0) //待压缩比例 *UIImagePNGRepresentation(image) *这是iOS自带的将UIImage对象转成NSData */ NSData *data = UIImageJPEGRepresentation(image,1.0); //开始上传 [formData appendPartWithFileData:data name:name fileName:fileName mimeType:@"image/jpg"]; } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { //根据responseObject获取后台返回的内容 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }];
/* * 多张上传和单张上传的区别: * 图片名要按时间+数组中图片的index+.jpg * 服务器保存图片的路径按时间+数组中图片的index */ //多张上传 //图片上传 for (int i = 0; i < imageArr.count; i ++) { [manager POST:@"http://112.54.80.235:50406/IndustryPioneer.svc/uploadimageIos" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { //创建待上传图片的名称,为了不重名,一般使用当前时间+图片类型(eg .png)为名称 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyyMMddHHmmss"; //服务器保存上传图片的路径,这个路径后台给的话用后台的,没给就自己创建(此处没给) NSString *name = [NSString stringWithFormat:@"%@%d",[formatter stringFromDate:[NSDate date]],i]; //后缀 .png .jpg NSString *fileName = [[NSString stringWithFormat:@"%@%d",[formatter stringFromDate:[NSDate date]],i] stringByAppendingString:@".jpg"]; /* *UIImageJPEGRepresentation(image,1.0) //待压缩比例 *UIImagePNGRepresentation(image) *这是iOS自带的将UIImage对象转成NSData */ NSData *data = UIImageJPEGRepresentation([imageArr objectAtIndex :i],1.0); //开始上传 [formData appendPartWithFileData:data name:name fileName:fileName mimeType:@"image/jpg"]; } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { //根据responseObject获取后台返回的内容 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }]; }
然后谈下2.x之前的版本和3.0以后的版本的区别
1、在3.0中NSURLConnection的API已废弃
AFNetworking 1.0建立在NSURLConnection的基础API之上 ,AFNetworking 2.0开始使用NSURLConnection的基础API ,以及较新基于NSURLSession的API的选项。 AFNetworking 3.0现已完全基于NSURLSession的API,这降低了维护的负担,同时支持苹果增强关于NSURLSession提供的任何额外功能。由于Xcode 7中,NSURLConnection的API已经正式被苹果弃用。虽然该API将继续运行,但将没有新功能将被添加,并且苹果已经通知所有基于网络的功能,以充分使NSURLSession向前发展。
AFNetworking 2.X将继续获得关键的隐患和安全补丁,但没有新的功能将被添加。Alamofire(Swift下的网络请求)软件基金会建议,所有的项目迁移到基于NSURLSession的API。
2、弃用的类
下面的类已从AFNetworking 3.0中废弃:
- AFURLConnectionOperation
- AFHTTPRequestOperation
- AFHTTPRequestOperationManager
3、修改的类
下面的类包含基于NSURLConnection的API的内部实现。他们已经被使用NSURLSession重构:
- UIImageView+AFNetworking
- UIWebView+AFNetworking
- UIButton+AFNetworking
4、2.x向3.x的过渡,迁移
AFHTTPRequestOperationManager 核心代码
如果你以前使用 AFHTTPRequestOperationManager , 你将需要迁移去使用 AFHTTPSessionManager。 以下的类在两者过渡间并没有变化:
- securityPolicy
- requestSerializer
- responseSerializer
接下来举一个关于AFHTTPSessionManager的简单例子。注意HTTP网络请求返回的不再是AFHTTPRequestOperation, 修改成为了NSURLSessionTask,并且成功和失败的Block块中的参数也变更为了NSURLSessionTask,而不再是AFHTTPRequestOperation。
AFNetworking 2.x
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"请求的url" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"成功");
} failure:^(AFHTTPRequestOperation *operation, NSError*error) {
NSLog(@"失败");
}];
AFNetworking 3.0
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
[session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"成功");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"失败");
}];
AFHTTPRequestOperation 核心代码
与NSURLConnection对象不同,每个共享应用范围的设置如会话管理、缓存策略、Cookie存储以及URL协议等,这些NSURLSession对象都可以单独进行配置。使用特定的配置来初始化会话,它可以发送任务来获取数据,并上传或下载文件。
在AFNetworking 2.0中,使用AFHTTPRequestOperation,有可能创建一个没有额外开销的独立的网络请求来获取数据。NSURLSession则需要更多的开销,为了获得所要请求的数据。
接下来,将要通过AFHTTPSessionManager创建一个对象,并创建一个任务和启动它。
AFNetworking 2.x
NSURL *URL = [NSURL URLWithString:@""];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];
AFNetworking 3.0
NSURL *URL = [NSURL URLWithString:@""];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:URL.absoluteString parameters:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
5、UIKit的迁移
图片下载已经被重构,以遵循AlamofireImage架构与新的AFImageDownloader类。这个类的图片下载职责的代理人是UIButton与UIImageView的类目,并且提供了一些方法,在必要时可以自定义。类别中,下载远程图片的实际方法没有改变。
UIWebView的类目被重构为使用AFHTTPSessionManager作为其网络请求。
6、UIAlertView的类目被废弃
从AFNetworking 3.0后UIAlertView的类目因过时而被废弃。并没有提供UIAlertController类目的计划,因为这是应用程序应处理的逻辑,而不是这个库。
最后说说HTTPS的问题
一般来讲如果app用了web service , 我们需要防止数据嗅探来保证数据安全.通常的做法是用ssl来连接以防止数据抓包和嗅探
其实这么做的话还是不够的 。 我们还需要防止中间人攻击(不明白的自己去百度)。攻击者通过伪造的ssl证书使app连接到了伪装的假冒的服务器上,这是个严重的问题!那么如何防止中间人攻击呢?首先web服务器必须提供一个ssl证书,需要一个 .crt 文件,然后设置app只能连接有效ssl证书的服务器。
一、HTTPS
HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。
二、App Transport Security
iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用了HTTPS协议进行传输。
一般我们如果还是使用的http,不更新的话,可通过在 Info.plist 中声明,倒退回不安全的网络请求。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
三、注意用HTTPS的话
These are the App Transport Security requirements:
- The protocol Transport Security Layer (TLS) must be at least version 1.2.
- Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
- Certificates must use at least an SHA256 fingerprint with either a 2048 bit or greater RSA key, or a 256 bit or greater Elliptic-Curve (ECC) key.
根据原文描述,首先必须要基于TLS 1.2版本协议。再来就是连接的加密方式要提供Forward Secrecy,文档中罗列出支持的加密算法(如下表)。最后就是证书至少要使用一个SHA256的指纹与任一个2048位或者更高位的RSA密钥,或者是256位或者更高位的ECC密钥。如果不符合其中一项,请求将被中断并返回nil。
-
第一条就是TLS版本所需要支持的协议
第一条满足
-
第一条就是连接的加密方式需要提供“Foward Secrecy”这个东东,下面是支持Forward Secrecy的加密方式
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256` TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
这是一个满足条件的证书信息中的情况
满足上面中的条件
但是也要注意证书的合法性,注意是否有效,iOS要求连接的HTTPS站点必须为CA签名过的合法证书。
证书不被信任
证书未经过验证
有效
注意以上不同的情况决定了AFSecurityPolicy--setAllowInvalidCertificates:
是否要验证证书的有效性。
四、使用AFNetworking中https的连接(3.0.0之前)
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
//validatesDomainName 是否需要验证域名,默认为YES;
securityPolicy.validatesDomainName = YES;
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager POST:urlString parameters:dic success:finishedBlock failure:failedBlock];
但是刚开始一直不行,不能正常访问数据
- 第一个错是我打开
Clarles
,忘记关了 - 老出现`Error: Error Domain=NSURLErrorDomain Code=-1012
第二种情况,在google逛了一大圈,试着好几种方法居然不行,????,特别是老看到The operation couldn’t be completed. (NSURLErrorDomain error -1012.)
,头都大了。。。
五、直到后来找到了一种解决方法...
步骤1:获取到站点的证书
我们可以使用以下openssl命令来获取到服务器的公开二进制证书(以google为例)
openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer
冒号中的为命令主要部分。该条命令将会在当前路径下,形成google.com站点的公开二进制证书,命名为https.cer。您可以将www.google.com 替换成您自己的站点以此来获取您自己站点的https.cer。
步骤2:导入到Xcode中
直接将https.cer放到资源目录中就好了,让我们可以通过pathForResource:
获取到证书,就OK了。
https
步骤3:代码导入
NSString *urlString = @"https://www.example.com/app/publicRequest";
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"https" ofType:@"cer"];
NSData * certData =[NSData dataWithContentsOfFile:cerPath];
NSSet * certSet = [[NSSet alloc] initWithObjects:certData, nil];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// 是否允许,NO-- 不允许无效的证书
[securityPolicy setAllowInvalidCertificates:YES];
// 设置证书
[securityPolicy setPinnedCertificates:certSet];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// request
[manager GET:urlString parameters:nil progress:^(NSProgress * progress){
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray * array = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"OK === %@",array);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"error ==%@",error.description);
}];
然后,就OK了。
六、注意下服务器端两种证书的区别
- 第一种是创建证书请求,然后到权威机构认证,随之配置到服务器;
- 第二种是自建证书,需要自己配置给服务器。
使用第一种还是好一点的,至少在我们 app 端不需要为ATS做过多的适配。但是如何才能知道一个HTTPS服务器是符合ATS特性中的要求的呢?使用nscurl命令:
nscurl --ats-diagnostics --verbose https://example.com
下面是测百度时,返回的某一段,结论是OK的,里面有很多测试的情况,我们可以逐一观察是否正确。
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
NSExceptionDomains = {
"www.baidu.com" = {
NSExceptionAllowsInsecureHTTPLoads = true;
NSExceptionMinimumTLSVersion = "TLSv1.0";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS