从 NSURLConnection 到 NSURLSession

前言

现如今的移动应用开发,网络模块几乎成了标配。如果你是早期 iOS 开发者的话,那么你对 NSURLConnection一定不会陌生。但其操作起来有许多不便,这也使得大家更愿意使用第三方库的解决方案,比如大名鼎鼎的 AFNetworking 你一定有所耳闻。正是因为这一点,苹果随着 iOS 7 的发布,也为开发者带来了改进后的原生网络库支持,那就是 NSURLSession。

今天,就让我来给你道一道从 NSURLConnectionNSURLSession 那些你知道和不知道的事。


NSURLConnection

NSURLConnection 作为 Core Foundation / CFNetwork 框架的 API 之上的一个抽象,在 2003 年,随着第一版的 Safari 的发布就发布了。NSURLConnection 这个名字,实际上是指代的 Foundation 框架的 URL 加载系统中一系列相关联的组件:NSURLRequestNSURLResponseNSURLProtocolNSURLCacheNSHTTPCookieStorageNSURLCredentialStorage 以及同名类 NSURLConnection

NSURLRequest 被传递给 NSURLConnection。被委托对象(遵守以前的非正式协议 <NSURLConnectionDelegate><NSURLConnectionDataDelegate>)异步地返回一个 NSURLResponse 以及包含服务器返回信息的 NSData

在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。

在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge),这可以由共享的 cookie 或机密存储(credential storage)来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的 NSURLProtocol 对象所拦截,以便在必要的时候无缝地改变其加载行为。

使用步骤

概览

(图片来自)

NSURL

创建一个 NSURL 对象,设置请求路径:

1
NSURL *url = [NSURL URLWithString:@"协议://主机地址/路径?参数&参数"];

解释如下:

  • 协议:不同的协议,代表着不同的资源查找方式、资源传输方式,比如常用的 HTTP、FTP 等
  • 主机地址:存放资源的主机的 IP 地址(域名)
  • 路径:资源在主机中的具体位置
  • 参数:参数可有可无,也可以多个。如果带参数的话,用 “?” 号后面接参数,多个参数的话之间用 “&” 隔开

NSURLRequest

创建一个 NSURLRequest 对象并传入 NSURL,设置请求头和请求体:

1
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];

参数解释如下:

  • requestWithURL:资源路径
  • cachePolicy:缓存策略(无论使用哪种缓存策略,都会在本地缓存数据),类型为枚举类型,取值如下:
    • NSURLRequestUseProtocolCachePolicy = 0 // 默认的缓存策略,使用协议的缓存策略
    • NSURLRequestReloadIgnoringLocalCacheData = 1 // 每次都从网络加载
    • NSURLRequestReturnCacheDataElseLoad = 2 // 返回缓存否则加载,很少使用
    • NSURLRequestReturnCacheDataDontLoad = 3 // 只返回缓存,没有也不加载,很少使用
  • timeoutInterval:超时时长,默认 60s

另外,还可以设置其它一些信息,比如请求头,请求体等等,如下:

1
2
3
4
5
// 告诉服务器数据为 JSON 类型
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
// 设置请求体(JSON类型)
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil];
request.HTTPBody = jsonData;

注意,上面的 request 应为 NSMutableURLRequest,即可变类型。(使用 POST 请求的话,那么就必须的使用 NSMutableURLRequest

NSURLConnection

使用 NSURLConnection 发送请求,通过返回 NSURLResponse 实例和 NSError 实例分析结果,接受服务器返回数据。

NSURLConnection 默认的请求类型为 GET,下面分异步和同步介绍 GET 的使用(POST 会在 NSURLSession 中介绍)。

异步请求

异步是指:在发送请求之后,一边在子线程中接收返回数据,一边执行之后的代码,当返回数据接收完毕后,采用回调的方式通知主线程做处理。同时,异步请求有两种实现方式。

1.使用 block:

1
2
3
4
5
6
7
8
9
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 有的时候,服务器访问正常,但是会没有数据
// 以下的 if 是比较标准的错误处理代码
if (connectionError != nil || data == nil) {
//给用户的提示信息
NSLog(@"网络不给力");
return;
}
}];

参数说明如下:

  • completionHandler:请求响应后(或者请求超时)执行的代码,queue 为代码添加到的队列,即 block执行的线程
    • NSURLResponse 为服务器的响应,真实类型为 NSHTTPURLResponse,通常只在「下载」功能时,才会使用;下面是协议头的参数:
      • URL:响应的 URL,有的时候,访问一个 URL 地址,服务器可能会出现重定向,会定位到新的地址
      • MIMEType(Content-Type):服务器告诉客户端,可以用什么软件打开二进制数据。网络之所以丰富多采,是因为有丰富的客户端软件。栗子:Windows 上提示安装 Flash 插件
      • expectedContentLength:预期的内容长度,要下载的文件长度,下载文件时非常有用
      • suggestedFilename:「建议」的文件名,方便用户直接保存,很多时候,用户并不关心要保存成什么名字
      • textEncodingName:文本的编码名称 @”UTF8”,大多数都是 UTF8
      • statusCode:状态码,在做下载操作的时候,需要判断一下
      • allHeaderFields:所有的响应头字典时候,用户并不关心要保存成什么名字
  • NSData:服务器返回的数据,例如:JSON、XML
  • NSError:网络访问错误码

2.使用代理(Delegate):

1
2
3
@interface ViewController ()<NSURLConnectionDataDelegate>

@end
1
2
3
NSURL * url = [NSURL URLWithString:@"http://itangqi.me"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];

使用代理可以监测下载过程:

1
2
3
4
5
6
7
8
9
10
11
12
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// 开始接收数据
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// 正在接收数据(会调用多次)
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// 接收数据失败
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
// 接收数据完成(成功|失败)
}

同步

同步是指:数据的请求在主线程来执行,一旦发送同步请求,直至服务器返回数据完成,才可以进行下一步操作,而网络数据加载需要一个时间过程,这样的话就会堵塞主线程,当然这种使用场景很少。

1
2
3
// 同步请求,代码会阻塞在这里一直等待服务器返回,如果 data 为 nil 则请求失败,当获取少量数据时可以使用此方法。
// request 参数同上。
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

NSURLSession

不管怎样,NSURLConnection 作为网络基础架构,已经服务了成千上万的 iOS 和 Mac OS 程序,并且做的还算相当不错。但是这些年,一些用例——尤其是在 iPhone 和 iPad 上面——已经对 NSURLConnection 的几个核心概念提出了挑战,让苹果有理由对它进行重构。

在 2013 的 WWDC 上,苹果推出了 NSURLConnection 的继任者:NSURLSession

NSURLConnection 一样,NSURLSession 指的也不仅是同名类 NSURLSession,还包括一系列相互关联的类。NSURLSession 包括了与之前相同的组件,NSURLRequestNSURLCache,但是把 NSURLConnection 替换成了 NSURLSessionNSURLSessionConfiguration 以及 NSURLSessionTask 的 3 个子类:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask

NSURLConnection 相比,NSURLsession 最直接的改进就是可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。每个 NSURLSession 对象都由一个 NSURLSessionConfiguration 对象来进行初始化,后者指定了刚才提到的那些策略以及一些用来增强移动设备上性能的新选项。

NSURLSession 中另一大块就是 session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。NSURLSessionTaskNSURLConnection 最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者 NSURLSession 这一公共委托者(common delegate)。

我们先来深入探讨 task,过后再来讨论 NSURLSessionConfiguration

概览

(图片来自)

Sessions

1
2
3
4
5
6
7
8
// 使用静态的 sharedSession 的方法,该类使用共享的 seesion,该 seesion 使用全局的 Cache,Cookie 和证书
+ (NSURLSession *)sharedSession;

// 根据 NSURLSessionConfiguration 创建对应配置的 seesion
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;

// 也是根据 NSURLSessionConfiguration 创建对应配置的 seesion,并且可以指定 seesion 的委托和委托所处的队列
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;

NSURLSessionTask

NSURLSession 本身是不会进行请求的,而是通过创建 task 的形式进行网络请求,同一个 NSURLSession 可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。那么我们就来看看 NSURLSession 都能创建哪些 task 吧。

Task 可以翻译为任务,那么在和网络请求相关的任务中,我们可以理解为:数据请求任务、下载任务、上传任务等。

其实从类名就能猜出它们各自的用途:

  1. NSURLSessionDataTask:使用这个 task 来调用 HTTP GET 方式请求,从服务器获取数据到内存。返回的数据格式是 NSData,可根据需要自行转换成 XML、JSON 等数据格式

  2. NSURLSessionUploadTask:使用这个 task 来上传磁盘文件到 web 服务器,典型地通过 HTTP POST 或者 PUT 方式

  3. NSURLSessionDownloadTask:使用这个 task 来从远程服务器下载文件到临时文件地址

  4. NSURLSessionStreamTask:使用这个 task 来执行异步的读和写

你也能暂停,恢复(开始)和取消 tasks。

NSURLSessionDataTask POST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 简单 Post 请求,POST 和 GET 请求在于对 request 的处理不同,其余和 GET 相同
*/

- (void)postWithSharedSession {
// 获取默认 Session
NSURLSession *session = [NSURLSession sharedSession];
// 创建 URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
// 创建 request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 请求方法
request.HTTPMethod = @"POST";
// 请求体
request.HTTPBody = [@"username=1234&pwd=4321" dataUsingEncoding:NSUTF8StringEncoding];
// 创建任务 task
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 获取数据后解析并输出
NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
}];
// 启动任务
[task resume];
}

NSURLSessionDataDelegate 代理方法

NSURLSession 提供了 block 的方式处理返回的数据,但是如果我们想要在接收数据的过程中处理数据,我们可以使用 NSURLSessionDataDelegate 的代理方法,分为接收响应、接收数据和请求完成三个阶段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 - (void)sessionDataDelegate {
// 创建带有代理方法的自定义 session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

// 创建任务
NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=1234&pwd=4321"]]];

// 启动任务
[task resume];
}

// 1. 接受到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
NSLog(@"任务完成");
// 必须设置对响应进行允许处理才会执行后面两个操作。
completionHandler(NSURLSessionResponseAllow);
}

// 2. 接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// 处理每次接收的数据
NSLog(@"%s",__func__);
}

// 3. 请求成功或者失败(如果失败,error有值)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// 请求完成,成功或者失败的处理
NSLog(@"SessionTask %s",__func__);
}

NSURLSessionConfiguration

NSURLSessionConfiguration 对象用于对 NSURLSession 对象进行初始化。从指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,你会发现使用 NSURLSessionConfiguration 可以找到几乎任何你想要进行配置的选项。其有三个类工厂方法:

  1. 默认模式(+defaultSessionConfiguration):返回一个标准的 configuration,工作模式类似于 NSURLConnection,可以使用缓存的 Cache,Cookie,证书(credential)

  2. 及时模式(+ephemeralSessionConfiguration): 临时 session 配置,与默认配置相比,这个配置不会使用缓存的 Cache,Cookie,证书,只会存在内存里,所以当程序退出时,所有的数据都会消失,这对于实现像秘密浏览这种功能来说是很理想的

  3. 后台模式(+backgroundSessionConfiguration):它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文

除了这三种预设的模式之外 NSURLSessionConfiguration 还可以进行很多的配置。 timeoutIntervalForRequesttimeoutIntervalForResource 可以控制网络操作的超时时间。 allowsCellularAccess 属性可以控制是否允许使用无线网络。HTTPAdditionalHeaders 可以指定 HTTP 请求头。

NSURLSessionConfiguration 几乎可以完成网络操作的大多数配置功能,并且这些配置都绑定到当前的 Session 中,我们一旦用配置好的 NSURLSessionConfiguration 初始化 NSURLSession 实例后,就不能修改这个 NSURLSession 相关的配置了。所以,一切的配置操作都放在初始化 NSURLSession 之前。


iOS 9

  1. 在 iOS 7 后,苹果推荐使用 NSURLSession,并在 iOS 9 中废弃了 NSURLConnection

  2. iOS 9 所有网络连接默认为 HTTPS 服务,为此推出 App Transport Security,详情可参见:Networking with NSURLSession


NSURLConnection vs. NSURLSession

  1. 后台上传和下载:只需在创建 NSURLSession 的时候配置一个选项,就能得到后台网络的所有好处。这样可以延长电池寿命,并且还支持 UIKit 的多 task,在进程间使用相同的委托模型

  2. 能够暂停和恢复网络操作:使用 NSURLSession API 能够暂停,停止,恢复所有的网络任务,再也完全不需要子类化 NSOperation

  3. 可配置的容器:对于 NSURLSession 里面的 requests 来说,每个NSURLSession 都是可配置的容器。举个例来说,假如你需要设置 HTTP header 选项,你只用做一次,session 里面的每个 request 就会有同样的配置

  4. 提高认证处理:认证是在一个指定的连接基础上完成的。在使用 NSURLConnection 时,如果发出一个访问,会返回一个任意的 request。此时,你就不能确切的知道哪个 request 收到了访问。而在 NSURLSession 中,就能用代理处理认证

  5. 丰富的代理模式:在处理认证的时候,NSURLConnection 有一些基于异步的 block 方法,但是它的代理方法就不能处理认证,不管请求是成功或是失败。在 NSURLSession 中,可以混合使用代理和 block 方法处理认证

  6. 上传和下载通过文件系统:它鼓励将数据(文件内容)从元数据(URL 和 settings)中分离出来

要点

  1. 请求的缓存策略使用 NSURLRequestReloadIgnoringCacheData,忽略本地缓存

  2. 服务器响应结束后,要记录 Etag,服务器内容和本地缓存对比是否变化的重要依据

  3. 在发送请求时,设置 If-None-Match,并且传入 etag

  4. 连接结束后,要判断响应头的状态码,如果是 304,说明本地缓存内容没有发生变化,此时可以使用本地缓存来加载数据,如果缓存文件被意外删除,程序依然运行但会报错,加载数据也失败

  5. GET 缓存的数据会保存在 Cache 目录中 /bundleId 下,Cache.db 中


参考

  • NSURLConnection Class Reference
  • NSURLSession Class Reference
  • From NSURLConnection to NSURLSession
  • iOS网络1——NSURLConnection使用详解
  • [iOS] NSURLSession
  • NSURLSession 网络库 - 原生系统送给我们的礼物
我知道是不会有人点的,但万一有人想不开呢?
  • 分享到:
  • 微博
  • QQ空间
  • 腾讯微博
  • 微信