ios网络请求

时间:2024-10-08 12:50:38

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的选择,可参考以下几点:

  1. 如果要传递大量数据,如文件上传,只能用POST请求。
  2. GET的安全性比POST要差些,如果包含机密或敏感信息,建议用POST。
  3. 如果仅仅是获取数据(数据查询),建议使用GET。
  4. 如果是增加、修改、删除数据,建议使用POST。

3、发送请求

      ios向服务器端发送请求,建立客户端与服务端的连接(NSURLConnection或NSURLSessionTask),连接的方式有两种:同步与异步。

  1. 同步连接:当建立同步连接时,请求发出去以后,等着后台返回数据。只要后台还没有返回数据,那么其他的操作都不能执行。对于代码来说,只要同步请求未结束,它下面的代码就不会执行。
  2. 异步连接:请求发出后,不用等待,即使后台的数据还没有返回,但仍然可以进行其他操作。在代码中的表现就是,发送了请求之后,即使数据未返回,它下边的代码也可以继续执行。异步实现的方式有两种:一种是通过代理,另一种是通过block回调。

4、获取服务器的返回数据。

      服务器在的到客户端的请求后,不管成功还是失败,都会返回响应的数据。如果是网络连接问题,会给出连接超时的信息。

      服务器的返回数据存放在NSURLResponse中,NSURLResponse包括响应头和响应体,我们就是从这个NSURLResponse中提取到所需要的数据。

示例:

  1. AFHTTPSessionManage *session = [AFHTTPSessionManager manager];
  2. [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject)
  3. {
  4. NSLog(@"成功");
  5. }
  6. failure:^(NSURLSessionDataTask *task, NSError *error)
  7. {
  8. NSLog(@"失败");
  9. }]

上边这段网络请求代码,工包括以下几个参数。

  • 请求方式
  • 请求的URL
  • 请求参数
  • 成功返回的block
  • 失败返回的block

       仅从这段代码来看,AFNetworking的应用非常简单。问题在于,实际项目中,在很多地方都会用到网络请求。在调试过程中,URL也需要不断地变化,比如,测试环境下地URL与生产环境地URL并不相同;网络请求地方法和接口也是多种多样的。对URL和请求方法,如果缺乏统一的管理,就会带来巨大的工作量。

善用URL宏定义

示例:

  1. //如果请求地址和端口发生变化,只需在这里修改
  2. #define SeverDomain @"http://192.168.10.10:8899"
  3. @define kOrderServerUrl SeverDomain@"prj/app/order"
  4. @define kMineServerUrl SeverDomain@"prj/app/mine"
  5. @define kStoreServerUrl SeverDomain@"prj/app/store"

URL接口应统一管理

关于URL的应用,在实际项目中,也会看到类似下边的代码。

  1. AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
  2. [session GET:[NSString stringWithFormat:@"%@%@",SeverDomain, @"prj/app/order"] parameters:nil progress:nil
  3. success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject)
  4. {
  5. NSLog(@"成功");
  6. }
  7. failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error)
  8. {
  9. NSLog(@"失败");
  10. }];

从代码所实现的功能来看,无可厚非;但从网络请求的维护性和潜在的问题来看,这段代码是不可取的,这时因为:

  • URL是网络请求与后台交互的接口,这个接口定义至关重要,应该放到一个.h文件中统一管理。
  • URL应该由宏定义来声明,而不是用字符串来拼接。二者最大的区别是,在调用宏定义时,xcode会自动跟随,如果拼写有误,编译会报错;而xcode不会检查字符串拼写错误,即使拼写错误,编译器也不会报错。

改进后的代码,示例如下,在.h文件中,通过宏定义声明URL,如下:

  1. #define SeverDomain @"http://192.168.10.172:8899/"
  2. @define kOrderServerUrl SeverDomain@"prj/app/order"

当然,字符串的拼接也可以调用方法stringByAppendingString:来实现。换一种表示方法,如下:

#define kOrderSeverUrl [SeverDomain stringByAppendingString:@"prj/app/order"]

在.m文件中,当发网络请求时,添加如下代码:

  1. AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
  2. [session GET: kOrderServerUrl parameters:nil progress:nil
  3. success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject){
  4. NSLog(@"成功");
  5. }
  6. failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error)
  7. {
  8. NSLog(@"失败");
  9. }]

AFNetworking的序列化问题

使用常规的AFNetworking访问网络,首先要创建AFHTTPSessionManager的实例对象,如下:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

所有的网络请求,均有manager发起。需要注意的是,默认提交的数据请求格式是二进制的。后台返回的数据格式是JSON,如下:

  1. //ios请求数据编码为二进制格式
  2. = [AFHTTPRequestSerializer serializer];
  3. //后台数据返回数据编码是JSON格式
  4. = [AFJSONResponserializer serializer];

      所谓默认的格式,意思是说,上面这两行代码可写可不写。不写这两行代码,默认的就是这两种格式。如果不是这两种格式,就得写代码了。比如,如果数据请求的编码是JSON的,需要将请求格式设置为:

 = [AFJSONRequestSerializer serializer];

如果后台返回的数据编码不是JSON,而是二进制格式,这时需要将数据响应格式设置为:

 = [AFHTTPResponserializer serializer];

AFNetworking请求格式

通过对requestSerializer的设置来区分AFNetworking数据请求的序列化格式,网络请求的序列化编码有以下三种

  1. AFHTTPRequestSerializer: 普通的HTTP编码格式,也可以理解为二进制格式,类似于“cellnumber = 186116198XXX&123456”,这种格式就是可在浏览器上直接访问的格式。
  2. AFJSONRequestSerializer:是JSON编码格式,请求格式类似于“{“cellnumber” :“186116198XXX”,"token" : "123456"}”。
  3. 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简单示例:

  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  2. NSURL *url = [NSURL URLWithString:@""];
  3. NSError *error;
  4. NSString *data = [NSString stringWithContentsOfURL:url encoding: NSUTF8StringEncoding error: &error];
  5. if(data != nil){
  6. dispatch_async(dispatch_get_main_queue(),^{
  7. NSLog(@"根据后台返回地数据,更新UI %@");
  8. })
  9. }else{
  10. NSLog(@"error when download:%@",error);
  11. }
  12. })

       这段代码中的一个重要的方法是dispatch_async。在处理耗时的操作时,比如,下载超大的文件,为避免界面冻屏,我们会另外开辟一个子线程请求网络数据,待下载完毕后,再通知主线程更新UI界面。这时候,用GCD来实现这个流程的操作比传统的NSThread、NSOperation方法都要简单,代码框架结构如下:

  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  2. //处理耗时的任务,如下载
  3. dispatch_async(dispatch_get_main_queue(), ^{
  4. //后台任务完成后,更新页面
  5. });
  6. });