ios请求概述
在ios开发中,网络请求并不是特比额复杂,这得益于一个给力的第三方库——AFNetworking的出现。
AFNetworking 1.0建立在NSURLConnection的基础API之上,AFNetworking 2.0开始使用NSURLConnection的基础API和部分NSURLSession基础之上的API。现有的AFNetworking 3.0版本已经完全基于NSURLSession的API,这样一来,不仅降低了代码维护的工作量,同时也支持NSURLSession提供的任何额外的功能。
影响网络请求的几个条件
确定网络请求的URL,URL的全称是Uniform Resource Locator(统一资源定位符),通过一个URL,能找到互联网上唯一的一个资源。网址就是资源,我们所需要的数据存在服务器端,app根据网址(NSURL)向后台发送请求(NSURLRequest)。
1、网络请求的方式
App与后台服务器之间的数据交互,是通过网络请求的方式来实现的。网络请求最常用的方法有两种:GET请求和POST请求。需要说明的是,网络请求并不是ios独有的。
2、GET请求和POST请求的区别
- GET请求的接口会包含参数部分,参数会作为网址的一部分,服务器地址与参数之间通过字符“?”来间隔;POST请求会将服务器地址与参数分开,请求接口中只有服务器地址,而参数会作为请求的一部分,提交给后台服务器。
- GET请求参数会出现在接口中,不安全;而POST请求相对安全。
- 虽然GET请求和POST请求都可以用来请求和提交数据,通常情况下GET多用于从后台请求数据,POST多用于向后台提交数据。
关于GET和POST的选择,可参考以下几点:
- 如果要传递大量数据,如文件上传,只能用POST请求。
- GET的安全性比POST要差些,如果包含机密或敏感信息,建议用POST。
- 如果仅仅是获取数据(数据查询),建议使用GET。
- 如果是增加、修改、删除数据,建议使用POST。
3、发送请求
ios向服务器端发送请求,建立客户端与服务端的连接(NSURLConnection或NSURLSessionTask),连接的方式有两种:同步与异步。
- 同步连接:当建立同步连接时,请求发出去以后,等着后台返回数据。只要后台还没有返回数据,那么其他的操作都不能执行。对于代码来说,只要同步请求未结束,它下面的代码就不会执行。
- 异步连接:请求发出后,不用等待,即使后台的数据还没有返回,但仍然可以进行其他操作。在代码中的表现就是,发送了请求之后,即使数据未返回,它下边的代码也可以继续执行。异步实现的方式有两种:一种是通过代理,另一种是通过block回调。
4、获取服务器的返回数据。
服务器在的到客户端的请求后,不管成功还是失败,都会返回响应的数据。如果是网络连接问题,会给出连接超时的信息。
服务器的返回数据存放在NSURLResponse中,NSURLResponse包括响应头和响应体,我们就是从这个NSURLResponse中提取到所需要的数据。
示例:
-
AFHTTPSessionManage *session = [AFHTTPSessionManager manager];
-
[session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject)
-
{
-
NSLog(@"成功");
-
}
-
failure:^(NSURLSessionDataTask *task, NSError *error)
-
{
-
NSLog(@"失败");
-
}]
上边这段网络请求代码,工包括以下几个参数。
- 请求方式
- 请求的URL
- 请求参数
- 成功返回的block
- 失败返回的block
仅从这段代码来看,AFNetworking的应用非常简单。问题在于,实际项目中,在很多地方都会用到网络请求。在调试过程中,URL也需要不断地变化,比如,测试环境下地URL与生产环境地URL并不相同;网络请求地方法和接口也是多种多样的。对URL和请求方法,如果缺乏统一的管理,就会带来巨大的工作量。
善用URL宏定义
示例:
-
//如果请求地址和端口发生变化,只需在这里修改
-
#define SeverDomain @"http://192.168.10.10:8899"
-
@define kOrderServerUrl SeverDomain@"prj/app/order"
-
@define kMineServerUrl SeverDomain@"prj/app/mine"
-
@define kStoreServerUrl SeverDomain@"prj/app/store"
URL接口应统一管理
关于URL的应用,在实际项目中,也会看到类似下边的代码。
-
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
-
[session GET:[NSString stringWithFormat:@"%@%@",SeverDomain, @"prj/app/order"] parameters:nil progress:nil
-
success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject)
-
{
-
NSLog(@"成功");
-
}
-
failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error)
-
{
-
NSLog(@"失败");
-
}];
从代码所实现的功能来看,无可厚非;但从网络请求的维护性和潜在的问题来看,这段代码是不可取的,这时因为:
- URL是网络请求与后台交互的接口,这个接口定义至关重要,应该放到一个.h文件中统一管理。
- URL应该由宏定义来声明,而不是用字符串来拼接。二者最大的区别是,在调用宏定义时,xcode会自动跟随,如果拼写有误,编译会报错;而xcode不会检查字符串拼写错误,即使拼写错误,编译器也不会报错。
改进后的代码,示例如下,在.h文件中,通过宏定义声明URL,如下:
-
#define SeverDomain @"http://192.168.10.172:8899/"
-
@define kOrderServerUrl SeverDomain@"prj/app/order"
当然,字符串的拼接也可以调用方法stringByAppendingString:来实现。换一种表示方法,如下:
#define kOrderSeverUrl [SeverDomain stringByAppendingString:@"prj/app/order"]
在.m文件中,当发网络请求时,添加如下代码:
-
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
-
[session GET: kOrderServerUrl parameters:nil progress:nil
-
success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject){
-
NSLog(@"成功");
-
}
-
failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error)
-
{
-
NSLog(@"失败");
-
}]
AFNetworking的序列化问题
使用常规的AFNetworking访问网络,首先要创建AFHTTPSessionManager的实例对象,如下:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
所有的网络请求,均有manager发起。需要注意的是,默认提交的数据请求格式是二进制的。后台返回的数据格式是JSON,如下:
-
//ios请求数据编码为二进制格式
-
= [AFHTTPRequestSerializer serializer];
-
//后台数据返回数据编码是JSON格式
-
= [AFJSONResponserializer serializer];
所谓默认的格式,意思是说,上面这两行代码可写可不写。不写这两行代码,默认的就是这两种格式。如果不是这两种格式,就得写代码了。比如,如果数据请求的编码是JSON的,需要将请求格式设置为:
= [AFJSONRequestSerializer serializer];
如果后台返回的数据编码不是JSON,而是二进制格式,这时需要将数据响应格式设置为:
= [AFHTTPResponserializer serializer];
AFNetworking请求格式
通过对requestSerializer的设置来区分AFNetworking数据请求的序列化格式,网络请求的序列化编码有以下三种
- AFHTTPRequestSerializer: 普通的HTTP编码格式,也可以理解为二进制格式,类似于“cellnumber = 186116198XXX&123456”,这种格式就是可在浏览器上直接访问的格式。
- AFJSONRequestSerializer:是JSON编码格式,请求格式类似于“{“cellnumber” :“186116198XXX”,"token" : "123456"}”。
- AFPropertyListRequestSerializer:属于plist格式,这种格式很少用,也可以理解为一种特殊的xml格式,解析起来相对容易
AFNeiworking响应格式
与网络请求格式相对应的是后台的数据响应格式,AFNetworking给出了以下几种数据响应格式。
- AFHTTPResponseSerializer:二进制格式
- AGJSONResponseSerializer:JSON格式
- AFXMLParserResponseSerializer:XML格式,只能返回XMLParser,还需要自己通过代理方法解析。
- AFXMLDocoumentResponseSerializer:Mac OS X格式
- AFImageResponseSerializer:Image格式
- AFCompoundResponseSerializer:组合格式
异步请求数据并刷新UI界面
在处理ios网络请求时,掌握多线程编程是必须的。应该说,只要是网络请求,都会遇到多线程的问题,不仅仅是ios要求这样,其他的比如android、java开发,都会遇到大量的后台运行、多线程池、异步消息队列等问题,这些都要运用多线程技术来实现。虽然多线程技术看起来高深,其实需要我们自己编写多线程代码的地方并不多,当我们调用ios SDK发起一个网络请求时,系统都会默认地自动开辟一个线程去处理。从而给人地感觉是,整个ios APP基本上就是在mian主线程中执行的。
App中所有的触发动作,都是用户在页面上操作控件完成的;用户触摸控件,触发新的事件,后台处理完成后,更新UI界面。只要是UI控件的更新,都是在主线程处理完成的。
开辟一个子进程,最典型的莫过于文件的下载,如离线地图的下载。下载地图时,每个省市的数据包是独立的,需要单独下载。我们可以同时下载多个省市的数据包,也可以一个接一个地下载。只要单击某个省市地下载按钮,系统就会启动一个新地子线程来请求网络数据。为了显示下载地进度,还需要边下载边更新主线程地UI,ios提供了GCD机制来完成多线程地下载。
GCD简单示例:
-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-
NSURL *url = [NSURL URLWithString:@""];
-
NSError *error;
-
NSString *data = [NSString stringWithContentsOfURL:url encoding: NSUTF8StringEncoding error: &error];
-
if(data != nil){
-
dispatch_async(dispatch_get_main_queue(),^{
-
NSLog(@"根据后台返回地数据,更新UI %@");
-
})
-
}else{
-
NSLog(@"error when download:%@",error);
-
}
-
})
这段代码中的一个重要的方法是dispatch_async。在处理耗时的操作时,比如,下载超大的文件,为避免界面冻屏,我们会另外开辟一个子线程请求网络数据,待下载完毕后,再通知主线程更新UI界面。这时候,用GCD来实现这个流程的操作比传统的NSThread、NSOperation方法都要简单,代码框架结构如下:
-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-
//处理耗时的任务,如下载
-
dispatch_async(dispatch_get_main_queue(), ^{
-
//后台任务完成后,更新页面
-
});
-
});