从 NSURLConnection 到 NSURLSession
2016-01-15 01:37
656 查看
iOS 7 和 Mac OS X 10.9 Mavericks 中一个显著的变化就是对 Foundation URL 加载系统的彻底重构。
现在已经有人在深入苹果的网络层基础架构的地方做研究了,所以我想是时候来分享一些对于我对于这些新的 API 的看法和心得了,新的 API 将如何影响我们编写程序,以及它们对于 API 设计理念的影响。
在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。
在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge),这可以由共享的 cookie 或机密存储(credential storage)来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的
不管怎样,
在 2013 的 WWDC 上,苹果推出了
和
与
我们先来深入探讨 task,过后再来讨论
当一个
所有的 task 都是可以取消,暂停或者恢复的。当一个 download task 取消时,可以通过选项来创建一个恢复数据(resume data),然后可以传递给下一次新创建的 download task,以便继续之前的下载。
不同于直接使用
Data task 可以通过
Upload task 的创建需要使用一个 request,另外加上一个要上传的
Download task 也需要一个 request,不同之处在于
以下是一些具体的观察:
在
由于增加了
想要查看更多关于后台 session 的信息,可以查看 WWDC Session 204: "What's New with Multitasking"
尽管在这个体系结构中,某些决定对于可组合性和可扩展性而言是一种倒退,但是
现在已经有人在深入苹果的网络层基础架构的地方做研究了,所以我想是时候来分享一些对于我对于这些新的 API 的看法和心得了,新的 API 将如何影响我们编写程序,以及它们对于 API 设计理念的影响。
NSURLConnection作为 Core Foundation / CFNetwork 框架的 API 之上的一个抽象,在 2003 年,随着第一版的 Safari 的发布就发布了。
NSURLConnection这个名字,实际上是指代的 Foundation 框架的 URL 加载系统中一系列有关联的组件:
NSURLRequest、
NSURLResponse、
NSURLProtocol、
NSURLCache、
NSHTTPCookieStorage、
NSURLCredentialStorage以及同名类
NSURLConnection。
NSURLRequest被传递给
NSURLConnection。被委托对象(遵守以前的非正式协议
<NSURLConnectionDelegate>和
<NSURLConnectionDataDelegate>)异步地返回一个
NSURLResponse以及包含服务器返回信息的
NSData。
在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。
在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge),这可以由共享的 cookie 或机密存储(credential storage)来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的
NSURLProtocol对象所拦截,以便在必要的时候无缝地改变其加载行为。
不管怎样,
NSURLConnection作为网络基础架构,已经服务了成千上万的 iOS 和 Mac OS 程序,并且做的还算相当不错。但是这些年,一些用例——尤其是在 iPhone 和 iPad 上面——已经对
NSURLConnection的几个核心概念提出了挑战,让苹果有理由对它进行重构。
在 2013 的 WWDC 上,苹果推出了
NSURLConnection的继任者:
NSURLSession。
和
NSURLConnection一样,
NSURLSession指的也不仅是同名类
NSURLSession,还包括一系列相互关联的类。
NSURLSession包括了与之前相同的组件,
NSURLRequest与
NSURLCache,但是把
NSURLConnection替换成了
NSURLSession、
NSURLSessionConfiguration以及
NSURLSessionTask的 3 个子类:
NSURLSessionDataTask,
NSURLSessionUploadTask,
NSURLSessionDownloadTask。
与
NSURLConnection相比,
NSURLsession最直接的改进就是可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。每个
NSURLSession对象都由一个
NSURLSessionConfiguration对象来进行初始化,后者指定了刚才提到的那些策略以及一些用来增强移动设备上性能的新选项。
NSURLSession中另一大块就是 session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。
NSURLSessionTask与
NSURLConnection最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者
NSURLSession这一公共委托者(common delegate)。
我们先来深入探讨 task,过后再来讨论
NSURLSessionConfiguration。
NSURLSessionTask
NSURLsessionTask是一个抽象类,其下有 3 个实体子类可以直接使用:
NSURLSessionDataTask、
NSURLSessionUploadTask、
NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。
当一个
NSURLSessionDataTask完成时,它会带有相关联的数据,而一个
NSURLSessionDownloadTask任务结束时,它会带回已下载文件的一个临时的文件路径。因为一般来说,服务端对于一个上传任务的响应也会有相关数据返回,所以
NSURLSessionUploadTask继承自
NSURLSessionDataTask。
所有的 task 都是可以取消,暂停或者恢复的。当一个 download task 取消时,可以通过选项来创建一个恢复数据(resume data),然后可以传递给下一次新创建的 download task,以便继续之前的下载。
不同于直接使用
alloc-init初始化方法,task 是由一个
NSURLSession创建的。每个 task 的构造方法都对应有或者没有
completionHandler这个 block 的两个版本,例如:有这样两个构造方法
–dataTaskWithRequest:和
–dataTaskWithRequest:completionHandler:。这与
NSURLConnection的
-sendAsynchronousRequest:queue:completionHandler:方法类似,通过指定
completionHandler这个 block 将创建一个隐式的 delegate,来替代该 task 原来的 delegate——session。对于需要 override 原有 session task 的 delegate 的默认行为的情况,我们需要使用这种不带
completionHandler的版本。
NSURLSessionTask 的工厂方法
在 iOS 5 中,NSURLConnection添加了
sendAsynchronousRequest:queue:completionHandler:这一方法,对于一次性使用的 request, 大大地简化代码,同时它也是
sendSynchronousRequest:returningResponse:error:这个方法的异步替代品:
NSURL *URL = [NSURL URLWithString:@"http://example.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { // ... }];
NSURLSession在 task 的构造方法上延续了这一模式。不同的是,这里不会立即运行 task,而是将该 task 对象先返回,允许我们进一步的配置,然后可以使用
resume方法来让它开始运行。
Data task 可以通过
NSURL或
NSURLRequest创建(使用前者相当于是使用一个对于该 URL 进行标准
GET请求的
NSURLRequest,这是一种快捷方法):
NSURL *URL = [NSURL URLWithString:@"http://example.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { // ... }]; [task resume];
Upload task 的创建需要使用一个 request,另外加上一个要上传的
NSData对象或者是一个本地文件的路径对应的
NSURL:
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSData *data = ...; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { // ... }]; [uploadTask resume];
Download task 也需要一个 request,不同之处在于
completionHandler这个 block。Data task 和 upload task 会在任务完成时一次性返回,但是 Download task 是将数据一点点地写入本地的临时文件。所以在
completionHandler这个 block 里,我们需要把文件从一个临时地址移动到一个永久的地址保存起来:
NSURL *URL = [NSURL URLWithString:@"http://example.com/file.zip"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler: ^(NSURL *location, NSURLResponse *response, NSError *error) { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath]; NSURL *newFileLocation = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]]; [[NSFileManager defaultManager] copyItemAtURL:location toURL:newFileLocation error:nil]; }]; [downloadTask resume];
NSURLSession 与 NSURLConnection 的 delegate 方法
总体而言,NSURLSession的 delegate 方法是
NSURLConnection的演化的十年中对于 ad-hoc 模式的一个显著改善。您可以查看这个映射表来进行一个完整的概览。
以下是一些具体的观察:
NSURLSession既拥有 seesion 的 delegate 方法,又拥有 task 的 delegate 方法用来处理鉴权查询。session 的 delegate 方法处理连接层的问题,诸如服务器信任,客户端证书的评估,NTLM 和 Kerberos 协议这类问题,而 task 的 delegate 则处理以网络请求为基础的问题,如 Basic,Digest,以及代理身份验证(Proxy authentication)等。
在
NSURLConnection中有两个 delegate 方法可以表明一个网络请求已经结束:
NSURLConnectionDataDelegate中的
-connectionDidFinishLoading:和
NSURLConnectionDelegate中的
-connection:didFailWithError:,而在
NSURLSession中改为一个 delegate 方法:
NSURLSessionTaskDelegate的
-URLSession:task:didCompleteWithError:
NSURLSession中表示传输多少字节的参数类型现在改为
int64_t,以前在
NSURLConnection中相应的参数的类型是
long long。
由于增加了
completionHandler:这个 block 作为参数,
NSURLSession实际上给 Foundation 框架引入了一种全新的模式。这种模式允许 delegate 方法可以安全地在主线程与运行,而不会阻塞主线程;Delgate 只需要简单地调用
dispatch_async就可以切换到后台进行相关的操作,然后在操作完成时调用
completionHandler即可。同时,它还可以有效地拥有多个返回值,而不需要我们使用笨拙的参数指针。以
NSURLSessionTaskDelegate的
-URLSession:task:didReceiveChallenge:completionHandler:方法来举例,
completionHandler接受两个参数:
NSURLSessionAuthChallengeDisposition和
NSURLCredential,前者为应对鉴权查询的策略,后者为需要使用的证书(仅当前者——应对鉴权查询的策略为使用证书,即
NSURLSessionAuthChallengeUseCredential时有效,否则该参数为
NULL)
NSURLSessionConfiguration
NSURLSessionConfiguration对象用于对
NSURLSession对象进行初始化。
NSURLSessionConfiguration对以前
NSMutableURLRequest所提供的网络请求层的设置选项进行了扩充,提供给我们相当大的灵活性和控制权。从指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,你会发现使用
NSURLSessionConfiguration可以找到几乎任何你想要进行配置的选项。
NSURLSession在初始化时会把配置它的
NSURLSessionConfiguration对象进行一次 copy,并保存到自己的
configuration属性中,而且这个属性是只读的。因此之后再修改最初配置 session 的那个 configuration 对象对于 session 是没有影响的。也就是说,configuration 只在初始化时被读取一次,之后都是不会变化的。
NSURLSessionConfiguration 的工厂方法
NSURLSessionConfiguration有三个类工厂方法,这很好地说明了
NSURLSession设计时所考虑的不同的使用场景。
+defaultSessionConfiguration返回一个标准的 configuration,这个配置实际上与
NSURLConnection的网络堆栈(networking stack)是一样的,具有相同的共享
NSHTTPCookieStorage,共享
NSURLCache和共享
NSURLCredentialStorage。
+ephemeralSessionConfiguration返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的。
+backgroundSessionConfiguration:(NSString *)identifier的独特之处在于,它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文。
想要查看更多关于后台 session 的信息,可以查看 WWDC Session 204: "What's New with Multitasking"
配置属性
NSURLSessionConfiguration拥有 20 个配置属性。熟练掌握这些配置属性的用处,可以让应用程序充分地利用其网络环境。
基本配置
HTTPAdditionalHeaders指定了一组默认的可以设置出站请求(outbound request)的数据头。这对于跨 session 共享信息,如内容类型,语言,用户代理和身份认证,是很有用的。
NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password]; NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding]; NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0]; NSString *authString = [NSString stringWithFormat:@"Basic %@", base64EncodedCredential]; NSString *userAgentString = @"AppName/com.example.app (iPhone 5s; iOS 7.0.2; Scale/2.0)"; configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json", @"Accept-Language": @"en", @"Authorization": authString, @"User-Agent": userAgentString};
networkServiceType对标准的网络流量,网络电话,语音,视频,以及由一个后台进程使用的流量进行了区分。大多数应用程序都不需要设置这个。
allowsCellularAccess和
discretionary被用于节省通过蜂窝网络连接的带宽。对于后台传输的情况,推荐大家使用
discretionary这个属性,而不是
allowsCellularAccess,因为前者会把 WiFi 和电源的可用性考虑在内。
timeoutIntervalForRequest和
timeoutIntervalForResource分别指定了对于请求和资源的超时间隔。许多开发人员试图使用
timeoutInterval去限制发送请求的总时间,但其实它真正的含义是:分组(packet)之间的时间。实际上我们应该使用
timeoutIntervalForResource来规定整体超时的总时间,但应该只将其用于后台传输,而不是用户实际上可能想要去等待的任何东西。
HTTPMaximumConnectionsPerHost是 Foundation 框架中 URL 加载系统的一个新的配置选项。它曾经被
NSURLConnection用于管理私有的连接池。现在有了
NSURLSession,开发者可以在需要时限制连接到特定主机的数量。
HTTPShouldUsePipelining这个属性在
NSMutableURLRequest下也有,它可以被用于开启 HTTP 管线化(HTTP pipelining),这可以显着降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的。
sessionSendsLaunchEvents是另一个新的属性,该属性指定该 session 是否应该从后台启动。
connectionProxyDictionary指定了 session 连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不需要配置这个属性。
Cookie 策略
HTTPCookieStorage存储了 session 所使用的 cookie。默认情况下会使用
NSHTTPCookieShorage的
+sharedHTTPCookieStorage这个单例对象,这与
NSURLConnection是相同的。
HTTPCookieAcceptPolicy决定了什么情况下 session 应该接受从服务器发出的 cookie。
HTTPShouldSetCookies指定了请求是否应该使用 session 存储的 cookie,即
HTTPCookieSorage属性的值。
安全策略
URLCredentialStorage存储了 session 所使用的证书。默认情况下会使用
NSURLCredentialStorage的
+sharedCredentialStorage这个单例对象,这与
NSURLConnection是相同的。
TLSMaximumSupportedProtocol和
TLSMinimumSupportedProtocol确定 session 是否支持 SSL 协议。
缓存策略
URLCache是 session 使用的缓存。默认情况下会使用
NSURLCache的
+sharedURLCache这个单例对象,这与
NSURLConnection是相同的。
requestCachePolicyspecifies when a cached response should be returned for a request. This is equivalent to
NSURLRequest -cachePolicy.
requestCachePolicy指定了一个请求的缓存响应应该在什么时候返回。这相当于
NSURLRequest的
-cachePolicy方法。
自定义协议
protocolClasses用来配置特定某个 session 所使用的自定义协议(该协议是
NSURLProtocol的子类)的数组。
结论
iOS 7 和 Mac OS X 10.9 Mavericks 中 URL 加载系统的变化,是对NSURLConnection进行深思熟虑后的一个自然而然的进化。总体而言,苹果的 Foundation 框架团队干了一件令人钦佩的的工作,他们研究并预测了移动开发者现有的和新兴的用例,创造了能够满足日常任务而且非常好用的 API 。
尽管在这个体系结构中,某些决定对于可组合性和可扩展性而言是一种倒退,但是
NSURLSession仍然是实现更高级别网络功能的一个强大的基础框架。
相关文章推荐
- [Javascript] Advanced Reduce: Composing Functions with Reduce
- 海量数据框架变迁——阿里巴巴上市背后的技术力量
- 2016/01
- 《C语言及程序设计初步》第35讲实践项目
- vc6.0 动态工具栏按钮
- *Longest Increasing Subsequence
- JAVA nio selector 入门
- vc 6.0 生成word报表
- Alamofire-Swift Networking网络库
- 创业一定要非常低调的进入市场
- 使用Nginx代理S3时,需要禁用URL解码。
- ios小数向上、下取整,计算结果向上、下取整
- 互联网创业十问?good(快速迭代、把握核心用户应对抄袭,不需要把商业模式考虑完备,4种失败的信号,失败者没资格说趁着年轻...)
- 【JS复习笔记】01 基本语法
- swift网络数据请求方法
- c# webBrowser 模拟键盘输入及后台执行js
- eventbus学习小结
- 应用锁(AppLocker)原理及代码实现
- Java 一个对象实例化过程
- 2016/1/14 作业 第一题 生成四位验证码 第二题 彩票机