iOS网络请求知识一二点

1. iOS网络请求的概述

       当前我们开发的应用,可以说几乎每个应用都要用到网络请求,简直可以称之为“无网络不应用”  。而在众多网络请求中, AFNetorking作为一个第三方的框架,可以说是应用最为广泛的,而且我们也可以基于AFNetorking进行一些适用于项目的二次封装,以便更好的达到自己的需求。

  2. AFNetworking的应用

      2.2.1  AFNetworking

       AFNetowking是一款颇受欢迎的第三方框架,所谓第三方,就是说这个网络请求不是苹果公司出品的。AFNetorking从发布之日起,就得到了众多iOS开发者的青睐。迄今,已经发展到了3.0版本,为了契合iOS新版本的升级,AFNetorking在3.0版本中删除了所有基于NSURLConnection API的支持。如果你之前使用过基于AFNetorking 2.0X版本中的NSURLConnection API,虽然还能用,但是还是建议升级到基于NSURLSession API的AFNetorking 3.0X版本。

        AFNetorking 3.0X正式支持iOS7, Mac OS X的10.9、watchOS2、tvOS9和Xcode7。AFNetorking 3.0版本中,NSURLConnection的API已经被弃用。

       为了便于深入理解AFNetworking的来龙去迈,还是从AFNetorking 1.0说起吧。
AFNetorking 1.0 是建立在NSURLConnection的基础API之上,AFNetorking 2.0开始使用NSURLConnection的基础API和部分NSURLSession基础之上的API。现有的AFNetorking 3.0版本已经完全基于NSURLSession的API,这样一来,不仅降低了维护代码的工作量,同时也支持NSURLSession提供的任何额外的功能。

        在Xcode7中, NSURLConnection API已经正式被苹果公司弃用,尽管基于NSURLConnection的API仍然可以正常运行,但不会再添加什么新功能了。苹果公司已经明确表示,今后所有与网络有关的功能,都是基于NSURLSession的API的基础上的。

       任何新的技术出现之前,原有的技术仍会持续一段时间,AFNetorking也不例外。AFNetworking  2.0X将继续获得关键的安全补丁,仍然可以放心使用,但不会增加新的功能了。

       下面的类已从AFNetorking 3.0中弃用。

         AFURLConnectionOperation

        AFHTTPRequestOperation

       AFHTTPRequestOperationManager

    AFNetworking 2.0X是这样请求数据的。

      AFHTTPOperationManager  *manager = [AFHTTPOperationManager manager];

      manager.responseSerializer = [AFHTTPResponseSerializer serializer];

      [manager GET:@"请求的url" parameters:nil success:^(AFHTTPRequestOperation *operation ,id responseObject){

NSLog(@"成功");

}

failure:^(AFHTTPRequestionOperation *operation, NSError *error){

NSLog(@"失败");

}];

在AFNetowrking 3.0中,HTTP网络请求返回的不再是AFHTTPRequestOperation,而是NSURLSessionDataTask,对于AFNetworking 3.0来说,网络请求代码示例如下:

  AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

    [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

NSLog(@"成功")};

failure:^(NSURLSessionDataTask *task, NSError *error){

NSLog(@"失败")};

];

2.2.2  AFNetworking 框架使用方法

        在工程中引入AFNetworking框架是一件颇为简单的事,这就得再用一个第三方管理工具--Cocoapods。在iOS项目开发中,稍微像回事的工程,动不动就得引进十来个第三方的。为了有效管理这些第三方,才会出现了“管理第三方的第三方”--Cocoapods。关于Cocoapods的安装和使用,这里不做过多描述,详细资料可参阅关于Cocoapods使用的文章。

      Cocoapods是iOS最常用的第三方类库管理工具,绝大部分有名的开源类库都支持Cocoapods.正因为AFNetworking支持Cocoapods,才使得应用起来非常简单。如果你已经安装好了Cocoapods,只需要以下几步就能轻松引入AFNetworking.

  (1) 创建podfile文件。 打开MAC电脑上的终端窗口,在指定的工程路径下创建podfile文件,命令是:
       touch podfile

       创建成功后,会自动生成一个podfile文件,它是一个文本文件,可自行编辑。需要注意的是,为确保特殊字符的兼容性,最好在Xcode中打开这个podfile文件进行编辑;如果用textEditor,会出现字符不识别现象,这个特殊的字符就是单引号的处理。

(2)编辑podfile,引入AFNetworking的脚本,如下所示。

         platform:ios,'8.0'

         pod 'AFNetworking'

         保存这个podfile文件并退出。

 (3) 下载AFNetworking。在MAC终端窗口,输入命令:

       pod install

   这个时候,等待安装完成吧。这过程需要几分钟的时间,就看网络是否给力了,如果出现异常,也不必惊慌,初学者在遇到错误时,第一反应就是从网上搜索答案,结果出现错误越来越多。殊不知,换个好一点的网络,所有的错误瞬间消失。

 (4)验证AFNetworking是否可以调用。在Cocoapods创建成功后,所有的工程会自动生成一个对应的xcworkspace工程(工作空间)。后续使用时,一定要打开这个以xcworkspace为后缀的工程文件。如果仍然打开xcodeproj文件,编译时会报错,原因就是缺少了AFNetworking这个文件造成的。

         验证AFNetworking能否正常工作的方法很简单,在工程文件中,添加以下代码。

      #import <AFNetworking.h>

  只需要引入一个AFNetworking API,编译一下,如果正常,说明成功;反之,失败;

2.2.3  影响网络请求的几个条件

        对于一个app来说,网络请求是必不可少的,而且会在多处用到。稍微复杂些的app,总觉得网络请求的模块很乱,既然说网络请求没有技术难点,那么,这种“乱”的感觉又是从何而来的呢?要解决这个问题,首先得了解下iOS网络请求的过程。

        确定网络请求的URL,URL的全称是 Uniform Resource Location(统一资源定位符),通过一个URL,能偶找到互联网上唯一的一个资源。网址就是资源,我们所需要的数据存在服务器端,App根据网址(NSURL)向后台发送请求(NSURLRequest)。

       1. 网络请求的方式

        APP与后台服务器之间的数据交换,是通过网络请求的方式来实现的。网络请求最常用的方法有两种:GET请求和POST请求。需要说明的是,网络请求并不是iOS所独有的,它是整个互联网通信共有的技术,可见其应用的广泛。对于iOS开发者来说,需要对GET和POST有一些基本的认识。

     2. GET请求和POST请求的区别

         GET请求的参数和POST请求的参数的区别

         1. GET请求的接口会包含参数部分,参数会作为网址的一部分,服务器地址与参数之间通过字符“?”来间隔;POST请求会将服务器地址与参数分开,请求接口中只有服务器地址,而参数会作为请求的一部分,提交后台服务器。

        2. GET请求参数会出现在接口中,不安全;而POST请求相对安全。

        3. 虽然GET请求和POST请求都可以用来请求和提交数据,但通常情况下GET多用于从后台请求数据,而POST多用于向后台提交数据。 

          关于GET和POST的选择,可参考以下几点:

        (1)如果要传递大量数据,如文件上传,只能用POST请求。

        (2) GET的安全性要比POST差些,如果包含机密或敏感信息,建议用POST。

        (3)如果是仅仅是获取数据(数据查询,建议用GET。

        (4)如果是增加、修改、删除数据,建议使用POST。

     3.发送请求

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

   (1)同步连接:当建立同步连接时,请求发送出去以后,等着后台返回数据。只要后台还没有返回数据,那么其他的操作都不能进行。对于代码来说,只要同步请求尚未结束,它下面的代码就不会执行。

   (2)异步连接:请求发送出去以后,不用等待,即便后台的数据还没有返回,但仍然可以进行其他操作。在代码中的表现是就是,发送了请求以后,即使数据未返回,它下面的代码也可以继续执行。异步实现的方式有两种:一种是通过代理(Delegate),另一种是通过Block回调。

  4. 获取服务器的返回数据

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

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

  在清楚了网络的请求机制之后,我们再来审视一下之前的这段代码示例:

  AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

    [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

   NSLog(@"成功")};

   failure:^(NSURLSessionDataTask *task, NSError *error){

   NSLog(@"失败")};

   ];

  上面这段网络请求的代码,共包括以下几个参数:

  1. 请求参数

  2. 请求的URL

  3. 请求的参数(Parameters)

  4. 成功返回的Block(success)

  5. 失败返回的Block(failure)。

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

2.2.4  善用URL宏定义

    在实际项目中,经常可以看到类似这样的代码。

  #define  kOrderServerUrl  @"http://192.168.10.172:8899/proj/app/order"

 #define   kMineServerUrl  @"http://192.168.10.172:8899/proj/app/mine"

 #define   kStoreServerUrl  @"http://192.168.10.172:8899/proj/app/store"

      乍一看,这段代码没有什么缺陷。在宏定义文件中,通过#define声明几个URL,又怎么了呢?

或许你已经发现,同样的代码,同样的IP地址,在多个地方重复出现,恐怕不妥吧!每当看到这类代码时,在我耳边时常想起“面向对象的编程思想”的警示。作为一名程序员,我们应该追求代码更加简洁,优雅。

        这里仅仅是给出一个代码片段,才不过三个URL而已。在实际项目中,时常多大几十个URL。通篇的相同的IP地址,不仅仅是带来审美的疲劳,更大的问题是:在开发阶段,这个IP地址需要经常变动,至少需要区分一下测试环境和生产环境。我们期望的是,在改动IP时,只需要改动一个地方,改动一次;而不是到处搜索(Search),到处替换 (Replace)。

        其实,很简单,只需要再添加一个宏定义。如果留意的话,你会发现,一个好的框架必然会用到大量的宏定义。宏定义的妙处,不在于语法上的替换,而是原本复杂的代码经过宏定义后,瞬间简洁了很多,改进后的代码如下:

       //如果请求地址和端口发生变化,只需要这里修改

      #define SeverDomain @"http://192.168.10.10:8899/"

      #define  kOrderServerUrl    SeverDomain@"proj/app/order"

      #define   kMineServerUrl    SeverDomain@"proj/app/mine"

      #define   kStoreServerUrl    SeverDomain@"proj/app/store"

     改进后,冗余的代码去掉了,如果请求地址和端口发生了变化,只需要在这里修改一次。这个看上去不起眼的改动,不仅减少了代码量,而且也避免了因改动多处而造成的低级错误。

   2.2.5 URL接口统一管理

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

        AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

        [session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

         NSLog(@"成功")};

        failure:^(NSURLSessionDataTask *task, NSError *error){

        NSLog(@"失败")};

        ];

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

       1.  URL是网络请求与后台交互的接口,这个接口定义至关重要,应该放到一个.h文件中统一管理。

       2. URL应该由宏定义来声明,而不是用字符串拼接的方式来构建。二者最大的区别就是,在调用宏定义时,Xcode会自动跟谁,如果拼写有误,编译会报错;而Xcode不会检查字符串拼写是否有误,即使拼写有误,编译器也不会报错。

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

      #define SeverDomain @"http://192.168.10.10:8899/"

      #define  kOrderServerUrl    SeverDomain@"proj/app/order"
     当然,字符串的拼接也可以调用方法 stringByAppendingString:来实现。换一种表示方法,如下所示。

     #define  kOrderServerUrl   [SeverDomain stringByAppendingString:@"proj/app/order"]

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

    AFHTTPSessionManager *session = [AFHTTPSessionManager manager];

        [session GET:kOrderServerUrl parameters:nil success:^(NSURLSessionDataTask *task, id responseObject){

         NSLog(@"成功")};

        failure:^(NSURLSessionDataTask *task, NSError *error){

        NSLog(@"失败")};

        ];

       通过以上几行代码的改进,你会发现,代码瞬间规整了许多,可能出现的低级错误从根源上得以灭绝。当团队多人协作共同完成一个App时,如果每个人都自觉遵循一个约定的编程风格,编码的世界该是有多么美好。    

     2.2.6  AFNetworking的二次封装

       网络请求之所以看起来复杂,主要是受到内外两方面因素的影响。从内部因素来讲,iOS自身的网络请求机制也在护短的改进,从早期的NSURLConnection到今天的NSURLSession;从外部因素来讲,以AFNetworking为代表的第三方网络库也在不断的升级。第三方的网络库再强大,也离不开iOS网络框架的支撑,所以,一旦iOS自身网络框架发生变化,第三方的网络库必然随之而变。

       既然AFNetworking已经很好用了,为何还要对它进行再封装呢?答案很简单,正是因为网络库经常变化,才做二次封装。

       对AFNetworking进行二次封装后,使用起来会更加方便,实用性更强。封装的原则是,对经常变化的部分(如网络库)进行封装,把封装之后的API提供给自己的工程使用。即使以后网络库更新了,我们也只需要更新这个封装好的网络库即可。   

       如果直接使用iOS原生的NSURLConnection或NSURLSession,也同样可以实现App的网络请求,但是只不过代码量无形中会增加很多,代码量一大,维护起来就很苦难。

      对于使用AFNetworking的开发者来说,如果是直接调用AFNetworking的API,这样不是很理想,无法做到整个工程的统一配置,最好的方式就是对网络层再封装一层,全工程不允许直接使用AFNetworking的API,必须调用我们自己封装的API,如此一来,任何网络配置都可以在这一层配置好,使用者无法知道里面实现的细节,只好调用就可以了。

       1. 对GET和POST请求的理解

       2. 对无线网络的判断

       3. 请求超时的时间配置

       4. 网络请求出现异常时,给出提示信息

       对于AFNetworking的二次封装,乍一听很在理,其实过度封装,也会带来局限性。很多情况下,这种封装是针对业务需要的,大可不必刻意地为封装而封装。

     2.3   AFNetworking的序列化问题

        谈到App与后台的接口调试,尤其是在初期阶段,着实让人头痛。在约定接口时,经常说的一句话就是,前端与后台都要通过HTTP通信。这句话看似简单,其实到了调试阶段则会变得复杂,这里给出一个精彩遇到的场景:就拿请求验证码来说,App通过POST方式将手机号发给后台,格式如下:

           请求参数={

          cellnumber = 186116198xx;

      }

      后台返回的数据显示,“手机号为空,请重新输入!”

      iOS通过输出log信息看出,分明已经提交给后台了,而且其他工程的代码也是这么做的啊,为什么偏偏对整个后台就报错了呢?

      与后台调试接口,难就难在后台也不会轻易“认错”。后台的调试方法是,直接通过浏览器地址栏输入请求的URL和请求的参数来验证后台是不是有问题。就拿比较简单的验证码来说,在浏览器地址栏输入"http://192.168.10.10:8899/getCode?cellnumber=186116198xxx"。短信验证码还真的收到了,这样说来,后台也没有问题啊。那么究竟是哪里出了问题呢?

       经过分析发现,这是因为iOS数据请求的序列化格式问题。后台可接收的虽然是HTTP协议格式,但具体到哪类数据格式又有区分了。这个时候,要重点查看以下的代码。

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

     AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

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

     //iOS请求数据编码为二进制格式

     manager.requestSerializer = [AFHTTPRequestSerializer serializer];

   //后台数据返回数据编码是JSON格式

    manager.responseSerializer = [AFJSONResponseSerializer serializer];

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

      manager.requestSerializer = [AFJSONRequestSerializer serializer];

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

     manager.responseSerializer = [AFHTTPResponseSerializer serializer];

 2.3.1    AFNetworking请求格式

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

       (1) AFHTTPRequestSerializer: 普通的HTTP编码格式,也可以理解为二进制格式,类似于"cellnumber=186116198xxx&token=123456",这种格式就是可以在浏览器上直接访问的格式。

      (2) AFJSONRequestSerializer: 是JSON编码格式,请求格式类似于"{"cellnumber":"186116198xxx","token":"123456"}"。

     (3)  AFPropertyListRequestSerializer: 属于plist格式,这种格式很少用,也可以理解为一种特殊的XML格式,解析起来相对容易。

       在AFNetworking开源库的AFURLRequestSerializer.h文件中,可以看出AFHTTPRequestSerializer与AFJSONRequestSerializer的关系:

       'AFJSONRequestSerializer' is a subclass of 'AFHTTPRequestSerializer' that encodes parameters as JSON using 'NSJSONSerializer',setting the 'Content-Type' of the encoded request to 'application/json'。

      意思是说,AFJSONRequestSerializer是AFHTTPRequestSerializer的子类,AFJSONRequestSerializer可以通过JSON数据格式请求后台,同时,将Content-Type的编码设为application/json类型,  设置方式如下。

      manager.responseSerializer.acceptableContentType = [NSSet setWithObjects:@"text/html",@"text/plain",@"application/json",nil];   

 2.3.2  AFNetworking响应格式

       与网络请求格式相对应的是后台的数据响应格式。AFNetworking给出了以下几种格式。

       1.  AFHTTPResponseSerializer:二进制格式

       2.  AFJSONResponseSerializer: JSON格式

       3.  AFXMLParserResponseSerializer: XML格式,只能返回XMLParser,还需要自己通过代理方法解析。

       4.  AFPropertyListResponseSerializer: Plist格式。

       5.  AFImageResponseSerializer: Image格式。

       6. AFCompoundResponseSerializer:组合形式。

      2.4  异步请求数据并刷新UI界面

        在处理iOS网络请求时,掌握多线程编程是必须的。应该说,只要是网络请求,都会遇到多线程的问题,不仅仅是iOS要求这样,其他的比如Android、Java开发,都会遇到大量的后台运行、多线程池、异步消息队列等问题,这些都要运用多线程技术来实现。虽然多线程技术看起来很高深,其实需要我们自己编写多线程的地方并不多,当我们调用iOS SDK发起一个网络请求时,系统都会默认地自动开辟一个线程去处理。从而给人的感觉就是,整个iOS APP基本上就是Main主线程中执行的。

       App中所有的触发动作,都是用户在页面上操作控件完成的,用户触摸控件,触发新的时间,后台处理完成后,更新UI界面。只要是UI控件的更新,都是在主线程处理完成的。

       开辟一个子线程,最经典的莫过于文件的下载,如离线地图的下载。下载地图时,每个省市的数据包都是独立的,需要单独下载。我们可以同时下载多个省市的数据包,也可以一个接一个的下载。只要单击某个省市的下载按钮,系统就会启动一个新的子线程来请求网络数据。为了显示下载速度,还需要边下载边更新主线程的UI,iOS提供了GCD机制来完成多线程的下载。

      GCD(Grand Central Dispatch)是Apple开发的一个多核编程的解决方法,这里给出了一个GCD应用的简单示例。

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

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

   

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        //处理耗时的任务,如下载

        dispatch_async(dispatch_get_main_queue(), ^{

            //后台任务完成,更新界面

        });

    })

  2.5  远程文件下载

     2.5.1 基于AFNetworking的文件下载

       每当谈到文件下载时,总会有一种望而生畏的感觉,同时下载多个文件怎么办?下载中网络出现异常情况,网络恢复后还能自动接着下载吗?对于好几个GB的超大文件能顺利下载吗?的确,网络下载可以做的很全面,也可以做的很简单,这取决于产品的业务需求。对于一个APP来说,下载都是为了业务服务的,而不是一个强大的下载工具。只要在一定的网络条件下,能满足业务需求就可以了。

        文件下载的实现方法,总体来讲有两种,一种是基于AFNetworking,另一种是基于原生的iOS SDK。前面讲到,AFNetworking也是基于iOS SDK封装的第三方库。iOS网络请求经历了NSURLConnection和NSURLSession两个阶段,于此相对应,AFNetworking 2.0与3.0版本均实现了文件的下载。我们与时俱进,对于AFNetworking 2.0不再赘述。通过AFNetworking 3.0实现文件的下载代码示意如下。   

        #import "ViewController.h"

       #import 'AFNetworking.h'

      @interface ViewController()

     {

        //下载操作

        NSURLSessionDownloadTask *_downloadTask;

    }

   @end

   @implementation ViewController

-(void)downFileFromServer

 {

         NSURL *URL = [NSURL URLWithString:@“http://www.baiidu.com/img/bdlogo.png”];

             NSURLSesstionConfiguration *configuration = [NSURLSesstionConfiguration defaultSesstionConfiguration];    

           //AFNetworking 3.0+基于URLSession封装

         AFURLSessionManager *manager = [AFURLSessionManager alloc]initWithSessionConfiguration:configuration];

       //请求    

       NSURLRequest *request = [NSURLRequest requestWithURL:URL];

      //下载操作

     _downloadTask = [manager downloadTaskWithRequest:request  progress:^(NSProgress *_Nonnull downloadProgress){

         //downloadProgress 的两个属性

         //@property int64_t totalUnitCount;需要下载文件的总大小

         //@property int64_t completedUnitCount;当前已经下载的大小

          //给Progress添加监听 KVC

          NSLog(@“下载的进度 = %f”,1.0*downloadProgress.completedUnitCount/downloadprogress.totalUnitCount)

         //切换到主线程刷新UI,通过progressView显示下载速度

        dispatch_async(dispatch_get_main_queue(),^{

            self.progressView.progress =  1.0*downloadProgress.completedUnitCount/downloadprogress.totalUnitCount;

        });

    destination:^NSURL *_Nonnull(NSURL *_Nonnull targetPath,NSURLResponse *_Nonnull response){

        //Block的返回值,需要返回一个URL,返回的这个URL就是文件下载后所在的路径

        NSString *cachesPath = [NSSearchPathForDirectoriesInDomain(NSCacheDirectories,NSUserDomainMask,YES) lastObject];

        NSSreing *path = [cachesPath StringByAppendingPathComponent:response.suggestedFilename];        

        return [NSURL fileURLWithPath:path];

    }

    completionHander:^(NSURLResponse *_Nonnull response,NSURL *_Nonable file Path,NSError *_Nonnull error){

        //filePath就是文件下载的路径:如果是zip文件,需要在这里解压缩

        NSString *imgFilePath = [filePath path];//将NSURL转换成NSString

        UIImage *img = [UIImage imageWithContentsOfFile:imgFilePath];        

        self.imageView.image = img;

    }];

        };

      以上这段代码实现了文件的下载和存储。这里需要注意NSURL的使用,在请求网络时,必须调用NSURL的URLWithString,这样才可以请求到网络的URL,示意代码如下:
      NSURL *URL = [NSURL URLWithString:@“http://www.baiidu.com/img/bdlogo.png”];

      而在访问本地文件时,必须调用NSURL的fileURLWithPath方法,示意代码如下:
       [NSURL fileURLWithPath:path];

     开始下载的方法为:

    [_downloadTask resume];

    暂停下载的方法为:

   [_downloadTask suspend];

    2.5.2 基于NSURLSesstion的文件下载

         关于iOS的文件下载,早期是基于NSURLConnection实现的,原本这种机制已经足够强大:正如人们常说的,没有最好只有更好。在iOS7,Apple官方推出了另外一种文件下载模式,这就是NSURLSession。有了这个神器,即使应用程序进入后台,也依然可以下载文件。

        与NSURLConnectio的使用类似,我们也要通过实现NSURLConnectio的代理方式来完成下载的任务。这个代理就是NSURLSessionDownloadDelegate,我们需要实现它的以下四个代理方法。

       - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                           didWriteData:(int64_t)bytesWritten

                                      totalBytesWritten:(int64_t)totalBytesWritten

                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

        -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompletedWithError:(NSError *)error;

         - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                      didResumeAtOffset:(int64_t)fileOffset

                                     expectedTotalBytes:(int64_t)expectedTotalBytes;

       - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                              didFinishDownloadingToURL:(NSURL *)location;

         当使用NSURLConnection时,每次网络请求都会创建一个链接;而使用NSURLSession时,每次网络请求都会创建一个 Session(会话),所以在用到NSURLSession时,首先要创建一个Configuration(配置),让这个Configuration运行在后台;这个Configuration的ID应该是唯一的,最好是应用程序的bundle identifier,如com.yourCompany.appName;然后基于Configuration创建一个Session,代码如下:

          NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"myUniqueAppID"];

            _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];

          创建了Configuration后,就可以初始化Session。在初始化Session时,设置好session的Delegete,并把delegateQueue设为mainQueue

          我们的目标是下载网络上的文件,一旦可以获取到网络的URL,就可以通过Session创建一个下载的任务,所以我们呢downloadTask是必不可少的,代码如下:

      //一个带有URL的请求

      NSURLSessionDownloadTask *task = [_session downloadTaskWithRequest:request];

     [task resume];

     当downloadTask开始下载文件时,它会把文件先下载到一个临时目录文件下,即使没有设置这个存储路径也没有关系,系统会自动创建,不用担心。

    在文件下载的过程中,它会告知下载的进度,已经下载了多少,我们再通过主进程的调用,通过下载的进度条提示给用户,代码如下:

          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                                           didWriteData:(int64_t)bytesWritten

                                      totalBytesWritten:(int64_t)totalBytesWritten

                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{

       CGFloat percentDone = (double)totalBytesWritten/ (double)totalBytesExpectedToWrite//通知下载的进度

}

      一旦文件下载完毕,就会调用以下Delegate方法。

      - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask

                              didFinishDownloadingToURL:(NSURL *)location{

        //文件下载完成后,将下载的临时文件存储到永久性的目录中,并删除临时文件。如果是压缩文件,解压的过程也是在这里完成的。
}

       以上就是基于NSURLSession实现文件下载的流程。总的来说,实现文件的下载没有想象的那么难。这一切,正是基于NSURLSession为我们提供强的后盾。

      2.5.3  网路安全访问设置

       从iOS9开始,苹果新增了App Transport Security(ATS),之前的网络请求是HTTP协议,现在都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用HTTPS协议了。这个改动的直接影响是,Xcode7默认的HTTPS泄题,如果仍然想进行HTTP请求,运行时就会出现如下错误。

        App Transport Security blocked a cleartext HTTP(http://) resource load  since it is insecure. Temporary  exceptions can be configured via your app's Info.plist file.

       系统告诉我们不能直接使用HTTP进行请求,需要在Info.plist文件中新增一段用于控制ATS的配置,方法如下。

       1. 在Info.plist中添加NSAppTransportSecurity类型Dictionary。

       2. 在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES。

       经过以上两步的设置后,Info.plist配置如下图所示

      这种基于通行界面的设置,看起来并不简单,经常出现错位的情况。如果不习惯这种操作,还可以采用代码编辑方式;在左侧工程导航栏,找到Info.plist文件,右键单击Open As-Source Code。添加NSAppTransportSecurity项,代码如下:

     

             2.6 小结

            在iOS网络请求的第三方框架中,AFNetworking一枝独秀。可以说,只要跟网络有关的,没有AFNetworking解决不了的。有了AFNetworking,原本看似神秘的网络操作,瞬间变得如此的简洁易用,以至于每一个iOS开发者都可以做到驾轻就熟。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值