原文作者:Mattt汤普森
链接:https://www.objc.io/issues/5-ios7/from-nsurlconnection-to-nsurlsession/
iOS7和Mac OS X10.9 Mavericks中一个显著的变化是对Foundation URL加载系统的彻底重构。
现在已有人深入苹果的网络层基础架构的地方做研究了,所以我想是时候分享一些我对于这些新的API的看法和心得了,新的API将如何影响我们的编写程序,以及它们对于API设计理念的影响。
NSURLConnection作为Core Foundation/CFNetwork框架的API之上的一个抽象,在2003年,随着第一版的Safari的发布就发布了。NSURLConnection这个名字,实际上是指代的Foundation框架的URL加载系统中一系列关联的组件:NSURLRequest、NSURLRespone、NSURLProtocol、NSHTTPCookieStorage、NSURLCredentialStorage以及同名类NSURLConnection。
NSURLRequest objects are passed to an NSURLConnection object. The delegate (conforming to the erstwhile informalandprotocols) responds asynchronously as an NSURLResponse, and any associated NSData are sent from the server.
NSURLRequest被传递给NSURLConnection。被委托对象(遵守以前的非正式协议
Before a request is sent to the server, the shared cache is consulted, and depending on the policy and availability, a cached response may be returned immediately and transparently. If no cached response is available, the request is made with the option to cache its response for any subsequent requests.
在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。
In the process of negotiating a request to a server, that server may issue an authentication challenge, which is either handled automatically by the shared cookie or credential storage, or by the connection delegate. Outgoing requests could also be intercepted by a registered NSURLProtocol object to seamlessly change loading behavior as necessary.
在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge),这可以由共享的 cookie 或机密存储(credential storage)来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的NSURLProtocol对象所拦截,以便在必要的时候无缝地改变其加载行为。
不管怎样,NSURLConnection作为网络基础架构,已经服务了成千上万的 iOS 和 Mac OS 程序,并且做的还算相当不错。但是这些年,一些用例——尤其是在 iPhone 和 iPad 上面——已经对NSURLConnection的几个核心概念提出了挑战,让苹果有理由对它进行重构。
At WWDC 2013, Apple unveiled the successor to NSURLConnection: NSURLSession.
在 2013 的 WWDC 上,苹果推出了NSURLConnection的继任者:NSURLSession。
Like NSURLConnection, NSURLSession refers to a group of interdependent classes, in addition to the eponymous class NSURLSession. NSURLSession is comprised of the same pieces as before, with NSURLRequest, NSURLCache, and the like, but replaces NSURLConnection with NSURLSession, NSURLSessionConfiguration, and three subclasses of NSURLSessionTask: NSURLSessionDataTask, NSURLSessionUploadTask, and NSURLSessionDownloadTask.
和NSURLConnection一样,NSURLSession指的也不仅是同名类NSURLSession,还包括一系列相关联的类。NSURLSession包括了与之前相同的组件,NSURLRequest与NSRULCache,但是把NSURLConnection替换成了NSURLSession、NSURLSessionConfiguration以及NSRULSessionTask的3个字类:NSURLSessionDataTask,NSRULSessionUploadTask,NSURLSessionDownloadTask。
The most immediate improvement NSURLSession provides over NSURLConnection is the ability to configure per-session cache, protocol, cookie, and credential policies, rather than sharing them across the app. This allows the networking infrastructure of frameworks and parts of the app to operate independently, without interfering with one another. Each NSURLSession object is initialized with an NSURLSessionConfiguration, which specifies these policies, as well a number of new options specifically added to improve performance on mobile devices.
与NSURLConnection相比,NSURLSession最直接的改进就是可以配置每个session的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。每个NSURLSession对象都由一个NSURLSessionConfiguration对象进行初始化,后者指定了刚才提到的那些策略以及一些用来增强移动设备上性能的新选项。
The other big part of NSURLSession is session tasks, which handle the loading of data, as well as uploading and downloading files and data between the client and server. NSURLSessionTask is most analogous to NSURLConnection in that it is responsible for loading data, with the main difference being that tasks share the common delegate of their parent NSURLSession.
NSURLSession中另一大块就是session task。它负责处理数据的加载以及文件的数据在客户端语服务器之间的上传和下载。NSURLSessionTask和NSURLConnection最大的相似之处在于它也负责数据中的加载,最大的不同之处在于所有的task共享其创造者NSURLSession (这一公共委托者common delgate)
We’ll dive into tasks first, and then talk more about session configuration later on.
我们先来深入探讨 task,过后再来讨论NSURLSessionConfiguration。
NSURLSessionTask
NSURLSessionTaskis an abstract subclass, with three concrete subclasses that are used directly:NSURLSessionDataTask,NSURLSessionUploadTask, andNSURLSessionDownloadTask. These three classes encapsulate the three essential networking tasks of modern applications: fetching data, such as JSON or XML, and uploading and downloading files.
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 = [NSURLURLWithString:@"http://example.com"];NSURLRequest*request = [NSURLRequestrequestWithURL:URL];
[NSURLConnectionsendAsynchronousRequest: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 = [NSURLURLWithString:@"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 = [NSURLURLWithString:@"http://example.com/upload"];
NSURLRequest*request = [NSURLRequestrequestWithURL:URL];
NSData*data = ...;
NSURLSession*session = [NSURLSessionsharedSession];
NSURLSessionUploadTask*uploadTask = [session uploadTaskWithRequest:request
fromData:datacompletionHandler:^(NSData*data, NSURLResponse *response,NSError*error) {
// ...
}];
[uploadTask resume];
Download task 也需要一个 request,不同之处在于completionHandler这个 block。Data task 和 upload task 会在任务完成时一次性返回,但是 Download task 是将数据一点点地写入本地的临时文件。所以在completionHandler这个 block 里,我们需要把文件从一个临时地址移动到一个永久的地址保存起来:
NSURL*URL = [NSURLURLWithString:@"http://example.com/file.zip"];
NSURLRequest*request = [NSURLRequest requestWithURL:URL];
NSURLSession*session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *newFileLocation = [NSURL fileURLWithPath:documentsPath];
[[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)
想要查看更多关于 session task 的信息,可以查看WWDC Session 705: "What’s New in Foundation Networking"
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 = [NSStringstringWithFormat:@"%@:%@", user, password];NSData* userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];NSString*base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];NSString*authString = [NSStringstringWithFormat:@"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 连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不需要配置这个属性。
关于连接代理的更多信息可以在CFProxySupportReference找到。
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 toNSURLRequest -cachePolicy.
requestCachePolicy指定了一个请求的缓存响应应该在什么时候返回。这相当于NSURLRequest的-cachePolicy方法。
自定义协议
protocolClasses用来配置特定某个 session 所使用的自定义协议(该协议是NSURLProtocol的子类)的数组。
结论
iOS 7 和 Mac OS X 10.9 Mavericks 中 URL 加载系统的变化,是对NSURLConnection进行深思熟虑后的一个自然而然的进化。总体而言,苹果的 Foundation 框架团队干了一件令人钦佩的的工作,他们研究并预测了移动开发者现有的和新兴的用例,创造了能够满足日常任务而且非常好用的 API 。
尽管在这个体系结构中,某些决定对于可组合性和可扩展性而言是一种倒退,但是NSURLSession仍然是实现更高级别网络功能的一个强大的基础框架。