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开发者都可以做到驾轻就熟。