每日一问19——NSURLSession

基本简介

2013 年 WWDC 上,Apple 发布了 NSURLConnection 的继任者 NSURLSession,支持 iOS7.0+,而 NSURLCOnnection 在 iOS9 被宣布弃用。

基本使用

设计思路与使用

NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务

NSURLSession.png

NSURLSession 以及相关类为 Http 请求提供了相关接口,为了使用 NSURLSession,需要我们创建一组 session 对象,每一个 session 对象负责执行一组数据传输任务。

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

最简单的请求

1.创建session对象,

NSURLSession *session = [NSURLSession sharedSession];

2.创建task对象

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
 NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

    }];

3.执行task

[task resume];
配置config
NSURLSessionConfiguration

在创建会话的时候,我们可以传入一个配置对象来配置当前会话的一些功能。并且系统提供以下三种会话配置供选择使用

@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;

+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
  • defaultSessionConfiguration
    返回一个标准的 configuration,这个配置实际上与 NSURLConnection 的网络堆栈(networking stack)是一样的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享 NSURLCredentialStorage。
  • ephemeralSessionConfiguration
    返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的
  • backgroundSessionConfiguration
    它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文
可配置的属性

NSURLSessionConfiguration提供了20多个可配置的属性,下面依次列举这些属性有什么用

  • requestCachePolicy : 指定一个请求的缓存响应在什么时候返回
NSURLRequestUseProtocolCachePolicy = 0, //指定URL缓存
     
     NSURLRequestReloadIgnoringLocalCacheData = 1, //从原始地址加载,不使用缓存。
     NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented 忽略本地缓存,也忽略代理服务器或其他中间介质的缓存。完全无缓存。
     NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
     
     NSURLRequestReturnCacheDataElseLoad = 2, 无论缓存是否过期,先使用本地缓存,如果无本地缓存,从原始地址加载数据
     NSURLRequestReturnCacheDataDontLoad = 3, 无论缓存是否过期,先使用本地缓存,如果本地无对应数据,那么从原始地址加载,视为请求失败。只取缓存。
     
     NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented  从原始地址确认缓存数据合法性后使用缓存,否则从原始地址加载

config.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
67179-f5592fa82c6fe4dc.png
  • timeoutIntervalForResource : 请求一个资源花费的最大时间
  • timeoutIntervalForRequest : 请求超时时间
  • networkServiceType : 区分使用流量的类型,网络流量,电话,语音,视频等
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
     {
     NSURLNetworkServiceTypeDefault = 0,    // Standard internet traffic
     NSURLNetworkServiceTypeVoIP = 1,    // Voice over IP control traffic
     NSURLNetworkServiceTypeVideo = 2,    // Video traffic
     NSURLNetworkServiceTypeBackground = 3, // Background traffic
     NSURLNetworkServiceTypeVoice = 4,       // Voice data
     NSURLNetworkServiceTypeCallSignaling API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) = 11, // Call Signaling
     };
  • allowsCellularAccess : 在蜂窝网络下是否允许连接
  • waitsForConnectivity : 让任务等待到有网络再执行,而不是立刻报错
  • discretionary : 决定后台任务是否能够为了系统最优性能,而被自由安排
  • sharedContainerIdentifier : 共享容器,为APP的共享插件提供session
  • sessionSendsLaunchEvents : 指定是否后台传输完成之后,启动或唤醒app
  • connectionProxyDictionary : 指定了 session 连接中的代理服务器,例如:
config.connectionProxyDictionary = @
     {
     @"HTTPEnable":@YES,
     (id)kCFStreamPropertyHTTPProxyHost:@"1.2.3.4",
     (id)kCFStreamPropertyHTTPProxyPort:@8080,
     @"HTTPSEnable":@YES,
     (id)kCFStreamPropertyHTTPSProxyHost:@"1.2.3.4",
     (id)kCFStreamPropertyHTTPSProxyPort:@8080
     };
  • TLSMinimumSupportedProtocol,TLSMaximumSupportedProtocol : 确定 session 是否支持 SSL 协议,这里指协议支持的最低和最高版本号
  • HTTPShouldUsePipelining : 开启 HTTP 管线化(HTTP pipelining),这可以显着降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的


    MbAOA.png
  • HTTPShouldSetCookies :请求是否应该使用 session 存储的 cookie,即 HTTPCookieSorage 属性的值
  • HTTPCookieAcceptPolicy : 决定了什么情况下 session 应该接受从服务器发出的 cookie
typedef NS_ENUM(NSUInteger, NSHTTPCookieAcceptPolicy) {
     NSHTTPCookieAcceptPolicyAlways,
     NSHTTPCookieAcceptPolicyNever,
     NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain
     };
  • HTTPAdditionalHeaders : 指定了一组默认的可以设置出站请求(outbound request)的数据头
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};
  • HTTPMaximumConnectionsPerHost : 限制连接到特定主机的数量
  • HTTPCookieStorage : 存储了 session 所使用的 cookie。默认情况下会使用 NSHTTPCookieShorage 的 +sharedHTTPCookieStorage 这个单例对象
  • URLCredentialStorage : 存储了 session 所使用的证书
  • URLCache : URLCache 是 session 使用的缓存
  • shouldUseExtendedBackgroundIdleMode : 使用后台模式,后台延迟回收tcp链接
  • protocolClasses : 用来配置特定某个 session 所使用的自定义协议,用户网络拦截,重定向,数据统计等
+ multipathServiceType : 多路径TCP
//     typedef NS_ENUM(NSInteger, NSURLSessionMultipathServiceType)
//     {
    //默认,不使用
//     NSURLSessionMultipathServiceTypeNone = 0,          /* None - no multipath (default) */
    //只有当主链路不能使用时才使用第二条链路
//    NSURLSessionMultipathServiceTypeHandover = 1,       /* Handover - secondary flows brought up when primary flow is not performing adequately. */
    //当主链路不够用时,丢包,延迟等情况,开启第二链路。
//    NSURLSessionMultipathServiceTypeInteractive = 2, /* Interactive - secondary flows created more aggressively. */
    //一起使用,仅开发测试使用
//    NSURLSessionMultipathServiceTypeAggregate = 3    /* Aggregate - multiple subflows used for greater bandwitdh. */
//     }

NSURLSession的代理

NSURLSessionDelegate.png
NSURLSessionDelegate

会话接收的最后一个消息。会话会变无效,因为系统性的错误或者明确的被指定无效,在这种情况下error的值为nil。

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error 

当一个认证挑战连接发生的时候,这个代理将给客户端一个机会去提供身份验证凭证(authenticationcredentials)。如果没有实现将执行默认的处理

(NSURLSessionAuthChallengePerformDefaultHandling)。
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler 

如果应用收到-application:handleEventsForBackgroundURLSession:completionHandler:的消息,这个代理方法将收到这个消息用以表明:该会话中,所有过去队列化的消息已经被交付。此时去调用completionHandler或者去进行内部更新后调用completionHandler都是安全的。

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session

NSURLSessionTaskDelegate

当请求被延迟执行时调用,可以选择撤销之前的请求并重新创建一个或者继续沿用之前的请求

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willBeginDelayedRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLSessionDelayedRequestDisposition disposition, NSURLRequest * _Nullable newRequest))completionHandler

当由于网络不通导致请求等待时,会调用这个方法,每个请求只会调一次,且后台请求不会调用

- (void)URLSession:(NSURLSession *)session taskIsWaitingForConnectivity:(NSURLSessionTask *)task 

客户端告知服务器端需要HTTP重定向。

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler 

该方法的调用取决于认证挑战的类型:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler 

当一个session task需要发送一个新的request body stream到服务器端的时候

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler 

周期性地通知代理发送到服务器端数据的进度

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend

当完成收集统计信息后调用。

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics 

告知该session task已经完成了数据传输任务

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error 

NSURLSessionDataDelegate

将NSURLSessionResponseDisposition的值传递给completionHandler进行回调,来控制任务的行为

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    /*
     typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
     NSURLSessionResponseCancel = 0,
     NSURLSessionResponseAllow = 1,
     NSURLSessionResponseBecomeDownload = 2,
     NSURLSessionResponseBecomeStream  = 3,
     }
     
     NSURLSessionResponseCancel:取消response的载入,同[task cancel].
     NSURLSessionResponseAllow:允许response的载入.
     NSURLSessionResponseBecomeDownload:把该request变为download task.
     NSURLSessionResponseBecomeStream:把该request变为stream task.
     */
}

通知这个data Task已经变成download task。

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask 

通知这个data Task已经变成stream task。

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask 

通知代理此次已经接收到的data。会分批吐出服务端返回的数据

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

将NSCachedURLResponse传递给completionHandler进行回调,去缓存响应的数据。或者传递nil,不去缓存。

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler

NSURLSessionDownloadDelegate

当下载任务完成的时候被调用,这个代理将把这个文件从被给的位置copy或者移动一个新的位置。因为这个文件将被移除,当这个代理消息返回的时候。 URLSession:task:didCompleteWithError:仍然会被调用

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location 

周期性的通知下载的进度

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 

告诉代理,下载任务重新开始下载了,如果一个下载任务因出错而失败,这个error中的userInfo字典中的NSURLSessionDownloadTaskResumeData对应的value,即为resume data。

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes 

NSURLSessionStreamDelegate

数据流的连接中读数据的一边已经关闭

- (void)URLSession:(NSURLSession *)session readClosedForStreamTask:(NSURLSessionStreamTask *)streamTask 

数据流的连接中写数据的一边已经关闭

- (void)URLSession:(NSURLSession *)session writeClosedForStreamTask:(NSURLSessionStreamTask *)streamTask

系统已经发现了一个更好的连接主机的路径

- (void)URLSession:(NSURLSession *)session betterRouteDiscoveredForStreamTask:(NSURLSessionStreamTask *)streamTask

task已经完成,并且从底层的网络连接中创建inputStream,outputStream。这将在所有队列化的I/O被完成后调用,此后这个streamTask将不接受任何代理消息

- (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask didBecomeInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream 

与NSURLConnection对比

普通的上传与下载
  • NSURLSession针对下载/上传等复杂的网络操作提供了专门的解决方案,针对普通、上传和下载分别对应三种不同的网络请求任务:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。
  • NSURLConnection通过配置NSURLRequest对象来决定请求的执行方式。
  • 当服务器返回的数据较小时,NSURLSession与NSURLConnection执行普通任务的操作步骤没有区别。
  • 执行上传任务时,NSURLSession与NSURLConnection一样需要设置POST请求的请求体进行上传。
下载任务方式
  • NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况。
  • 而使用NSURLSessionUploadTask下载文件,会默认下载到沙盒中的tem文件中,不会出现内存暴涨的情况,但是在下载完成后会把tem中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码。
请求方法的控制
  • NSURLConnection实例化对象,实例化开始,默认请求就发送(同步发送),不需要调用start方法。而cancel可以停止请求的发送,停止后不能继续访问,需要创建新的请求。
  • NSURLSession有三个控制方法,取消(cancel)、暂停(suspend)、继续(resume),暂停以后可以通过继续恢复当前的请求任务。
断点续传的方式
  • NSURLConnection进行断点下载,通过设置访问请求的HTTPHeaderField的Range属性,开启运行循环,NSURLConnection的代理方法作为运行循环的事件源,接收到下载数据时代理方法就会持续调用,并使用NSOutputStream管道流进行数据保存。
  • NSURLSession进行断点下载,当暂停下载任务后,如果downloadTask(下载任务)为非空,调用cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler这个方法,这个方法接收一个参数,完成处理代码块,这个代码块有一个NSData参数resumeData,如果resumeData非空,我们就保存这个对象到视图控制器的resumeData属性中,在点击再次下载时,通过调用[ [self.session downloadTaskWithResumeData:self.resumeData]resume]方法进行继续下载操作
配置信息
  • NSURLSession的构造方法(sessionWithConfiguration:delegate:delegateQueue)中有一个NSURLSessionConfiguration类的参数可以设置配置信息,其决定了cookie,安全和高速缓存策略,最大主机连接数,资源管理,网络超时等配置。
  • NSURLConnection不能进行这个配置。

相关文章

iOS 网络学习(二)—— NSURLSession
AFNetworking原理:NSURLSession(四)
从 NSURLConnection 到 NSURLSession
AFNetworking源码阅读(三)
NSURLSession与NSURLConnection区别

推荐阅读更多精彩内容