阅读 2265

OC中的NSURLSession

1. 概述

NSURLSession是在NSURLConnection之后出来的用来取代NSURLConnection进行网络请求的。NSURLSession并不单单指这个类本身,它实际上是指代 Foundation框架的URL加载系统中一些列相关的类和协议,它主要由NSURLSessionConfigurationNSURLSessionNSURLSessionTask这三个类及其子类以及相关的协议构成。

其中NSURLSessionConfiguration用于配置可用网络、Cookie、安全性、缓存策略、自定义协议、启动事件等选项,用来对NSURLSession对象进行初始。NSURLSession主要负责网络的请求与响应。NSURLSessionTask用于创建各种获取数据、上传、下载等任务并执行任务。

2. NSURLSessionConfiguration

2.1 初始化

NSURLSessionConfiguration对象用于对NSURLSession进行初始化。NSURLSessionConfiguration有3种初始化方式。

2.1.1 第一种
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
复制代码

这种方式创建的config是一个标准的配置,具有共享NSHTTPCookieStorage、共享NSURLCache、共享NSURLCredentialStorage

2.1.2 第二种
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
复制代码

这种方式创建的config不会对缓存、Cookie和证书进行持久性存储。当这种方式创建的session释放时或者收到内存警告时或者程序退出时,其缓存的相关信息就会被清除。

2.1.3 第三种
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"abc"];
复制代码

这种方式创建的config用于创建一个可以后台执行的session,当我们需要在程序挂起或退出的情况下继续进行上传或下载任务,就可以用这种方式来创建。这种方式创建config时需要带一个唯一标识(identifier),这个标示不能为nil或空字符串,当程序退出后重新启动时可以通过这个标示来恢复未完成的下载或上传任务。

2.2 属性介绍

2.2.1 identifier(唯一标识)
@property (nullable, readonly, copy) NSString *identifier;
复制代码

identifier是只读的,只能通过backgroundSessionConfigurationWithIdentifier:这个方法初始化时传进来,这个标示不能为nil或空字符串,当程序退出后重新启动时可以通过这个标示来恢复未完成的下载或上传任务。

2.2.2 requestCachePolicy(缓存策略)
@property NSURLRequestCachePolicy requestCachePolicy;
复制代码

requestCachePolicy是缓存策略,是一个枚举类型,如下所示:

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    // 默认的缓存策略,如果缓存不存在,直接从服务端获取,如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如:Cache-Control字段为must-revalidata,则询问服务端该数据时否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端。
    NSURLRequestUseProtocolCachePolicy = 0,
    
    // 忽略本地缓存数据,直接请求服务端
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
    
    // 忽略本地缓存,代理服务器以及其他中介,直接请求源服务器
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    
    // 有缓存就用它,不管其有效性(忽略Cache-Control字段),没有就请求服务器
    NSURLRequestReturnCacheDataElseLoad = 2,
    
    // 只从本地缓存加载,本地缓存没有就不作操作(无网络时使用)
    NSURLRequestReturnCacheDataDontLoad = 3,

    // 缓存数据必须得到服务端确认有效才使用
    NSURLRequestReloadRevalidatingCacheData = 5,
};
复制代码
2.2.3 timeoutIntervalForRequest(请求超时时间)
@property NSTimeInterval timeoutIntervalForRequest;
复制代码

用于设置请求超时时长,默认是60s。

2.2.4 timeoutIntervalForResource(资源超时时间)
@property NSTimeInterval timeoutIntervalForResource;
复制代码

用于设置资源超时时长,默认是7天,也就是说资源要在7天内到达。

2.2.5 networkServiceType(网络传输类型)
@property NSURLRequestNetworkServiceType networkServiceType;
复制代码

指定网络传输类型,一般就用默认的。

typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
{
    // 默认值,普通网络传输
    NSURLNetworkServiceTypeDefault = 0,
    
    // 网络语音通信传输,只能用于IP语音流
    NSURLNetworkServiceTypeVoIP = 1,
    
    // 影像传输
    NSURLNetworkServiceTypeVideo = 2,
    
    // 网络后台传输
    NSURLNetworkServiceTypeBackground = 3,
    
    // 语音传输
    NSURLNetworkServiceTypeVoice = 4,
    
    // 指定请求是针对响应性(时间敏感)数据的
    NSURLNetworkServiceTypeResponsiveData = 6, 
    NSURLNetworkServiceTypeAVStreaming = 8 ,
    NSURLNetworkServiceTypeResponsiveAV = 9, 
    NSURLNetworkServiceTypeCallSignaling = 11, 
};
复制代码
2.2.6 allowsCellularAccess(是否使用蜂窝网络)
@property BOOL allowsCellularAccess;
复制代码

指定是否使用蜂窝网络,默认为YES。比如在用户看视频时没有开启WiFi,就可以提示用户是否使用流量。

2.2.7 allowsExpensiveNetworkAccess(是否使用昂贵的网络)
@property BOOL allowsExpensiveNetworkAccess;
复制代码

这是iOS 13新增的一个属性,按照字面理解就是是否允许使用昂贵的网络,在连接蜂窝数据或个人热点时生效,系统自动判断。(默认是YES)。

2.2.8 allowsConstrainedNetworkAccess(是否使用受限制的网络)
@property BOOL allowsConstrainedNetworkAccess;
复制代码

这也是iOS 13新增的一个属性,按照字面理解就是是否允许使用受限制的网络,在开启低流量模式时使用,用户可以设置。(默认是YES)。

2.2.9 waitsForConnectivity(是否等待连接)
@property BOOL waitsForConnectivity;
复制代码

这个属性用于设置是否等待网络连接可用时再执行网络请求任务,默认是NO。比如我现在只连接了蜂窝网络,但是APP用户设置的不允许通过蜂窝网络进行网络请求,如果waitsForConnectivity这个属性为YES,那么发起请求时就不会立马返回网络连接失败的error,而是会等待网络可用时(比如连接上了WiFi)再执行网络请求,这种情况下设置的timeoutIntervalForRequest是不生效的,而timeoutIntervalForResource是有效的。

2.2.10 discretionary
@property (getter=isDiscretionary) BOOL discretionary;
复制代码

用于确定是否可以根据系统的判断来调度后台任务以获得最佳性能。

2.2.11 sharedContainerIdentifier
@property (nullable, copy) NSString *sharedContainerIdentifier;
复制代码

如果应用扩展(什么是应用扩展?)在后台创建了NSURLSession任务,那就必须设置一个共享容器,以确保应用扩展和载体应用实现数据共享,sharedContainerIdentifier就是用来指定共享容器的标示,然后我们就可以通过该标示符获取到共享容器。

2.2.12 sessionSendsLaunchEvents
@property BOOL sessionSendsLaunchEvents;
复制代码

用于设置在传输完成时是否应该在后台继续或启动应用程序。默认为YES,并且只有通过+backgroundSessionConfigurationWithIdentifier:创建NSURLSessionConfiguration时才有效。

2.2.13 connectionProxyDictionary
@property (nullable, copy) NSDictionary *connectionProxyDictionary;
复制代码

包含有关在此session中使用的代理信息的字典。

2.2.14 TLSMinimumSupportedProtocol
@property SSLProtocol TLSMinimumSupportedProtocol;
复制代码

请求支持的最低 TLS 版本,默认值是 kSSLProtocol3,即 SSL3.0。

2.2.15 TLSMaximumSupportedProtocol
请求支持的最高 TLS 版本,默认值是 kTLSProtocol12,即 TLS1.2。

@property SSLProtocol TLSMaximumSupportedProtocol;
复制代码
2.2.16 HTTPShouldUsePipelining
@property BOOL HTTPShouldUsePipelining;
复制代码

用于开启 HTTP 流水线(HTTP pipelining),可以显着减少请求的加载时间,但是由于没有被服务器广泛支持,默认是 NO 的。

2.2.17 HTTPShouldSetCookies
@property BOOL HTTPShouldSetCookies;
复制代码

指定了请求是否应该使用 Session 存储的 Cookie,即 HTTPCookieStorage 属性的值。

2.2.18 HTTPCookieAcceptPolicy
@property NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy;

typedef NS_ENUM(NSUInteger, NSHTTPCookieAcceptPolicy) {
    NSHTTPCookieAcceptPolicyAlways, // 接受所有的cookies
    NSHTTPCookieAcceptPolicyNever, // 拒绝所有的cookies
    NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain // 只接受从主文档中域的cookie
};
复制代码

决定了什么情况下 Session 应该接受从服务器发出的 Cookie。

2.2.19 HTTPAdditionalHeaders
@property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;
复制代码

是一个字典,包含了需要额外添加到session中的reques请求头(header),默认为空。如果在HTTPAdditionalHeaders额外添加的头部字段与NSURLRequest中重复了,则优先使用NSURLRequest对象中的请求头部字段。

NSURLSession已经默认给NSURLRequest添加一些请求头部字段,包括AuthorizationConnectionHostProxy-AuthenticateProxy-AuthorizationWWW-Authenticate

HTTPAddtionalHeaders可以额外添加的请求头部字段包括AcceptAccept-LanguageUser-Agent等。

2.2.20 HTTPMaximumConnectionsPerHost
@property NSInteger HTTPMaximumConnectionsPerHost;
复制代码

指定session内可以同时连接一个主机的最大连接数。

2.2.21 HTTPCookieStorage
@property (nullable, retain) NSHTTPCookieStorage *HTTPCookieStorage;
复制代码

存储了session所使用的cookie。通过defaultSessionConfigurationbackgroundSessionConfigurationWithIdentifier:创建的config会使用NSHTTPCookieStorage+ sharedHTTPCookieStorage单例,要清除存储的cookie,直接set为nil即可。通过ephemeralSessionConfiguration创建的config``cookie仅仅储存到内存,session失效时会自动清除。

2.2.22 URLCredentialStorage
@property (nullable, retain) NSURLCredentialStorage *URLCredentialStorage;
复制代码

证书存储。通过defaultSessionConfigurationbackgroundSessionConfigurationWithIdentifier:创建的config会使用NSURLCredentialStorage+ sharedCredentialStorage单例,要清除存储的证书,直接set为nil即可。通过ephemeralSessionConfiguration创建的config证书仅仅储存到内存,session失效时会自动清除。

2.2.23 URLCache
@property (nullable, retain) NSURLCache *URLCache;
复制代码

缓存NSURLRequest的response。默认的configuration,默认值的是sharedURLCache,后台的configuration,默认值是nil。短暂的configuration,cache存于内存,session失效,cache自动清除。

2.2.24 shouldUseExtendedBackgroundIdleMode
@property BOOL shouldUseExtendedBackgroundIdleMode;
复制代码

为创建的任何tcp套接字启用扩展后台空闲模式。 启用此模式会要求系统保持打开状态,并在进程移动到后台时延迟回收。

2.2.25 protocolClasses
@property(copy) NSArray<Class> *protocolClasses;
复制代码

用来配置特定某个session所使用的自定义协议(该协议是 NSURLProtocol 的子类)的数组。

2.2.26 multipathServiceType
@property NSURLSessionMultipathServiceType multipathServiceType;

typedef NS_ENUM(NSInteger, NSURLSessionMultipathServiceType)
{
    NSURLSessionMultipathServiceTypeNone = 0, // 不使用多路径TCP服务(默认)
    NSURLSessionMultipathServiceTypeHandover = 1, // 多路径TCP服务,提供Wi-Fi和蜂窝之间的无缝切换,以保持连接。
    NSURLSessionMultipathServiceTypeInteractive = 2, // 尝试使用最低延迟接口的服务
    NSURLSessionMultipathServiceTypeAggregate = 3 //聚合其他多路径选项容量的服务,旨在提高吞吐量和最小化延迟。
}
复制代码

指定通过 Wi-Fi 和 蜂窝网络传输数据的多路径 TCP 的连接策略。

3. NSURLSession

我们可以使用NSURLSession的API来创建一个或多个session对象,每个session对象都可以管理多个网络请求任务。创建会话(session)的方式有3种:

3.1 获取全局session

NSURLSession *session = [NSURLSession sharedSession];
复制代码

通过这种方式获取全局的NSURLSession对象。

3.2 指定NSURLSessionConfiguration创建session

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
复制代码

先创建一个NSURLSessionConfiguration对象config,然后根据config来创建session

3.3 创建带代理的session

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
                                                          delegate:self
                                                     delegateQueue:[NSOperationQueue new]];
复制代码

这种方式同样需要一个NSURLSessionConfiguration对象,并且要遵循NSURLSessionDelegate协议。

4 NSURLSessionTask

NSURLSessionTask是一个抽象类,它包含3个子类:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask。它们的继承关系如下:

NSURLSessionTask继承关系

NSURLSessionTask的数据返回方式主要有两种,一种是回调(completionHandler),另一种是代理。不管是哪一种task,其创建都是基于NSURLSession对象,创建task后一定要调用[task resume]来启动任务。

4.1 NSURLSessionDataTask

NSURLSessionDataTask继承自NSURLSessionTask,主要用于读取服务端的简单数据,比如JSON数据。创建NSURLSessionDataTask有4种方式,分别是基于NSURLNSURLRequest对象来创建,每种又分为带回调和不带回调2种。

    NSURL *url = [NSURL URLWithString:@"http://aaa.bbb/test/login"];
    
    // 注意,请求对象内部默认已经包含了请求头和请求方法(GET)
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    NSURLSession *session = [NSURLSession sharedSession];
    
    // 第一种:基于NSURL创建不带回调的task
    NSURLSessionDataTask *task1 = [session dataTaskWithURL:url];
    
    // 第二种:基于NSURLRequest创建不带回调的task
    NSURLSessionDataTask *task2 = [session dataTaskWithRequest:request];
    
    // 第三种:基于NSURL创建带回调的task
    NSURLSessionDataTask *task3 = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    // 第四种:基于NSURLRequest创建带回调的task
    NSURLSessionDataTask *task4 = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    // 每个task都要调用resume方法才会执行task
    [task1 resume];
复制代码

4.2 NSURLSessionUploadTask

NSURLSessionUploadTask继承自NSURLSessionDataTask,主要用于 向服务器上传文件类型的数据。有5种创建任务的方法。

// 第一、二种,通过文件路径上传(一种带回调一种不带回调),这种方式上传不需要把文件内容全部加载到内存中
- (void)NSURLSessionUploadTask1{
    NSURL *fileUrl = [NSURL fileURLWithPath:@"/user/file/abc.txt"];
    
    NSURL *url = [NSURL URLWithString:@"http://aaa.bbb/test/upload"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
    
    NSURLSession *session = [NSURLSession sharedSession];
    
    // 不带回调
    NSURLSessionUploadTask *task  = [session uploadTaskWithRequest:request fromFile:fileUrl];
    
    // 带回调
    NSURLSessionUploadTask *task  = [session uploadTaskWithRequest:request fromFile:fileUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    [task resume];
}
复制代码
// 第三、四种,上传NSData(带回调和不带回调两种),这种方式上传文件时需要先将文件加载到内存中
- (void)NSURLSessionUploadTask2{
    NSData *data = [NSData dataWithContentsOfFile:@"/user/file/abc.txt"];
    
    NSURL *url = [NSURL URLWithString:@"http://aaa.bbb/test/upload"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)data.length] forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
    
    NSURLSession *session = [NSURLSession sharedSession];
    
    // 不带回调
    NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:data];
    
    // 带回调
    NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
     [task resume];
       
}
复制代码
// 第五种,通过表单上传,将要上传的数据二进制写入httpbody中,然后将其添加到请求头中
- (void)NSURLSessionUploadTask3{
    
    NSURL *url = [NSURL URLWithString:@"http://aaa.bbb/test/upload"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:@"/user/file/abc.txt"];
    [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)data.length] forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
    
    
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionUploadTask *task = [session uploadTaskWithStreamedRequest:request];
    [task resume];
}
复制代码

4.3 NSURLSessionDownloadTask

NSURLSessionDownloadTask继承自NSURLSessionTask,主要用于文件下载。有6种创建下载任务的方式。需要注意的是文件下载保存的是一个临时文件,下载完后要将其拷贝到自己想要存储的目录再删除临时文件。

- (void)NSURLSessionDownloadTaskTest{
    NSString *urlStr = @"http://aaa.bbb/test/downloadFile.zip";
    NSURL *url = [NSURL URLWithString:urlStr];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
   
    
    // 不需要后台下载的话就用其他方式初始化config
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"abcd"];
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    
    // 第一种,通过一个NSURL对象来创建task(不带回调)
    NSURLSessionDownloadTask *task1 = [session downloadTaskWithURL:url];
    
    // 第二种,通过一个NSURL对象来创建task(带回调)
    NSURLSessionDownloadTask *task2 = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    // 第三种,通过一个NSURLRequest对象来创建task(不带回调)
    NSURLSessionDownloadTask *task3 = [session downloadTaskWithRequest:request];
    
    // 第四种,通过一个NSURLRequest对象来创建task(带回调)
    NSURLSessionDownloadTask *task4 = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    // 第五种,通过一个resumeData(保存在沙盒中的下载进度)数据来创建task(不带回调)
    NSString *resumePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"123.tmp"];
    NSData *resumeData = [NSData dataWithContentsOfFile:resumePath];
    NSURLSessionDownloadTask *task5 = [session downloadTaskWithResumeData:resumeData];

    // 第六种,通过一个resumeData数据来创建task(带回调)
    NSURLSessionDownloadTask *task6 = [session downloadTaskWithResumeData:resumeData completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    
    // 启动任务
    [task1 resume];
}
复制代码

当暂停下载时会产生一个记录下载进度的数据resumeData通过回调传过来,我们要将这个数据保存在沙盒中,以便继续下载时可以通过这个数据来创建下载任务(断点续传)。

// 暂停下载
- (void)pauseDownload {
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        // 记录已下载的数据
        // 把下载进度数据保存到沙盒中
        NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"123.tmp"];
        [self.resumData writeToFile:path atomically:YES];
    }];
}
复制代码

5. NSURLSessionDelegate

相关代理主要有四个:NSURLSessionDelegateNSURLSessionTaskDelegateNSURLSessionDataDelegateNSURLSessionDownloadDelegate。它们的继承关系如下:

NSURLSessionDelegate继承关系

5.1 NSURLSessionDelegate

NSURLSessionDelegate用于处理session级别的事件,比如session的生命周期和服务端请求验证客户端的身份或证书。

5.1.1 处理session的生命周期

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;
复制代码

session失效时会调用这个代理方法,有以下两种方法使session失效。

  • - (void)finishTasksAndInvalidate;session将等到所有task结束或失败后才调用这个委托方法。
  • - (void)invalidateAndCancel;session将直接取消所有正在执行的task,立即调用此委托方法。

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;
复制代码

如果是通过backgroundSessionConfigurationWithIdentifier:方式创建的configuration,再通过这个configuration创建的session用于管理可以后台执行的上传或下载任务,当后台传输完成时就会调用上面这个代理方法。

当后台传输完成时,如果此时APP处于未启动状态,那么APP会自动在后台重新启动,启动后会调用UIApplicationDelegate中下面这个代理方法,里面包含一个会话标示符(identifier)和completionHandler,我们首先要将completionHandler保存起来,然后根据identifier重新生成configuration,然后通过这个configuration创建的session将重新关联对应的后台任务。

// UIApplicationDelegate中的方法
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler;
复制代码
5.1.2 处理session身份验证

当服务器请求客户端证书或请求验证NTLM身份验证时,或者使用SSL或TLS连接服务器时都会调用下面这个代理方法。

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
    /*
     challenge是一个封装了服务端发过来的验证请求的对象,通过下面这个方法可以获取验证方式,验证方式有如下几种情况:
     NSURLAuthenticationMethodClientCertificate 表示要求验证客户端的证书
     NSURLAuthenticationMethodNegotiate 使用Kerberos或NTLM身份验证
     NSURLAuthenticationMethodNTLM 使用NTLM身份验证
     NSURLAuthenticationMethodServerTrust 验证服务端提供的证书
     */
    NSString *authenticationMethod = [challenge.protectionSpace authenticationMethod];
    
    /*
     completionHandler回调要传2个参数:
     第一个参数是一个枚举:
     typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {
         NSURLSessionAuthChallengeUseCredential = 0, // 指明通过另一个参数 credential 提供证书
         NSURLSessionAuthChallengePerformDefaultHandling = 1, // 相当于未执行代理方法,使用默认的处理方式,不使用参数 credential
         NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, // 取消整个请求,提供的凭证参数被忽略
         NSURLSessionAuthChallengeRejectProtectionSpace = 3, // 拒绝该 protectionSpace 的验证,不使用参数 credential
     }

     第二个参数:
     第二个参数为NSURLCredential类型,它是一种身份验证凭证,包含特定于凭证类型的信息和用于使用的持久存储类型。
     当第一个参数为NSURLSessionAuthChallengeUseCredential时,第二个参数必须传身份验证的凭据,否则就传nil;
        通过NSURLCredential可以创建3种类型的credential:
        1.当authenticationMethod的值为NSURLAuthenticationMethodHTTPBasic 或 NSURLAuthenticationMethodHTTPDigest时,调用以下方法来创建:
        - (instancetype)initWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence;
        + (NSURLCredential *)credentialWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence;
        2.当authenticationMethod的值为NSURLAuthenticationMethodClientCertificate时,调用以下方法来创建:
        - (instancetype)initWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence;
        + (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence;
        3.当authenticationMethod的值为NSURLAuthenticationMethodServerTrust时,调用以下方法来创建:
        - (instancetype)initWithTrust:(SecTrustRef)trust;
        + (NSURLCredential *)credentialForTrust:(SecTrustRef)trust;

     */
}
复制代码

如果上面这个代理方法没有实现的话就会调用NSURLSessionTaskDelegate的下面这个方法:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                            didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
                              completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    /*
     这里的authenticationMethod有以下几种可能
     NSURLAuthenticationMethodDefault 默认的验证
     NSURLAuthenticationMethodHTMLForm 不会用于 URL Loading System,在通过 web 表单验证时可能用到
     NSURLAuthenticationMethodHTTPBasic 基本的 HTTP 验证,通过 NSURLCredential 对象提供用户名和密码,相当于 Default 默认的验证
     NSURLAuthenticationMethodHTTPDigest 类似于基本的 HTTP 验证,摘要会自动生成,同样通过 NSURLCredential 对象提供用户名和密码
     */
    NSString *authenticationMethod = [challenge.protectionSpace authenticationMethod];                              
}
复制代码

5.2 NSURLSessionTaskDelegate

NSURLSessionTaskDelegate用于处理task级别的事件。

5.2.1 处理task生命周期
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
didCompleteWithError:(NSError *)error;
复制代码

当任务完成时会回到这个方法。要注意的是这里的error只有客户端的错误,比如无法解析主机名或连接到主机。服务端的错误是不会通过这个error传过来的。

5.2.2 处理task的重定向
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
willPerformHTTPRedirection:(NSHTTPURLResponse *)response 
        newRequest:(NSURLRequest *)request 
 completionHandler:(void (^)(NSURLRequest *))completionHandler;
复制代码

当服务器请求HTTP重定向时会调用这个方法。注意这个方法只适用于defaultSessionConfigurationephemeralSessionConfigurationsession生成的sessionbackgroundSessionConfigurationWithIdentifier:生成的session会自动重定向,而不会走这个方法。

  • response是服务器对原始请求的响应
  • request是新的NSURLRequest
  • completionHandler回调可以传一个重定向的NSURLRequest对象,也可以传nil,传nil的话就不进行重定向。
5.2.3 处理上传task
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
   didSendBodyData:(int64_t)bytesSent 
    totalBytesSent:(int64_t)totalBytesSent 
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
复制代码

执行上传任务时,会定期调用这个代理方法来告知上传进度。

  • bytesSent是从上次调用这个方法开始到这次调用这个方法期间发送的字节数。
  • totalBytesSent是已经发送的总的字节数。
  • totalBytesExpectedToSend是要上传的数据的总长度。

- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
复制代码

当我们通过uploadTaskWithStreamedRequest:这个方法来创建任务时,必须要实现这个代理方法,当调用这个方法时,需要通过completionHandler回调传入NSInputStream对象。因为上传可能失败,当失败时就会调用这个代理方法,就需要在这个方法里面重新提供请求体流。

5.2.4 处理身份认证
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
复制代码

这个代理方法用于处理task级别的身份验证,当session级别的身份认证代理函数没有实现的话就会走这个代理,具体请求前面已经介绍过了,这里不再赘述。

5.2.5 处理延迟和等待任务
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
willBeginDelayedRequest:(NSURLRequest *)request 
 completionHandler:(void (^)(NSURLSessionDelayedRequestDisposition disposition, NSURLRequest *newRequest))completionHandler;
 
 // disposition的枚举类型
 typedef NS_ENUM(NSInteger, NSURLSessionDelayedRequestDisposition) {
    NSURLSessionDelayedRequestContinueLoading = 0, // 继续执行原始请求
    NSURLSessionDelayedRequestUseNewRequest = 1, // 使用新请求执行下载
    NSURLSessionDelayedRequestCancel = 2, // 取消该任务
}
复制代码

当一个延迟启动的任务(比如设置了earliestBeginDate这个属性)准备启动时就会调用这个代理方法。只有在等待网络加载并且需要被新的请求替换时才需要实现这个委托方法。

通过completionHandler回调告诉task如何处理。当dispositionNSURLSessionDelayedRequestUseNewRequest时需要同时传一个新的NSURLRequest对象。


- (void)URLSession:(NSURLSession *)session 
taskIsWaitingForConnectivity:(NSURLSessionTask *)task;
复制代码

NSURLSessionConfigurationwaitsForConnectivity属性设置为YES并且无法获得足够的连接性时就会调用这个代理方法。比如当前用户只连了蜂窝网络,但用户又设置了只能在WiFi连接时才能看视频,当用户播放视频时就会调用这个代理方法,那我们就可以在这个代理方法里面更新UI提示用户连接WiFi。要注意每个任务最多调用此方法一次,并且只在连接最初不可用时调用。

5.2.6 收集任务的执行情况
- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task 
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics;
复制代码

当任务执行完成后,可以通过这个方法知道任务的执行情况,比如任务执行的时长、重定向次数等信息放在NSURLSessionTaskMetrics对象里面。

5.3 NSURLSessionDataDelegate

NSURLSessionDataDelegate继承自NSURLSessionTaskDelegate,主要用来处理dataTask数据(比如接收到响应,接收到数据,是否缓存数据等)。

5.3.1 处理task生命周期
- (void)URLSession:(NSURLSession *)session 
          dataTask:(NSURLSessionDataTask *)dataTask 
didReceiveResponse:(NSURLResponse *)response 
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
 
 // disposition的枚举类型
 typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
    NSURLSessionResponseCancel = 0, // 该task会被取消
    NSURLSessionResponseAllow = 1, // 该task正常进行
    NSURLSessionResponseBecomeDownload = 2, // 转成一个downloadTask
    NSURLSessionResponseBecomeStream // 转成一个StreamTask
}
复制代码

DataTask收到响应时,会调用这个代理方法。我们需要通过completionHandler回调指明任务后续执行方式。

  • 当传入的dispositionNSURLSessionResponseBecomeDownload时,是将task转换为download task,同时会调用URLSession:dataTask:didBecomeDownloadTask:这个代理方法。
  • 当传入的dispositionNSURLSessionResponseBecomeStream时,是将task转换为StreamTask,同时会调用URLSession:dataTask:didBecomeStreamTask:这个代理方法。

当我们的requestcontent-type支持multipart/x-mixed-replace 时,服务器会将数据分片传回来,而且每次传回来的数据会覆盖之前的数据。每次返回新的数据时,都会调用这个方法个,我们应该在这个函数中合理地处理先前的数据,否则会被新数据覆盖。如果我们没有提供该方法的实现,那么session将会继续任务,也就是说会覆盖之前的数据。


- (void)URLSession:(NSURLSession *)session 
          dataTask:(NSURLSessionDataTask *)dataTask 
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;
复制代码

上面已经介绍了什么时候会调用这个代理方法。


- (void)URLSession:(NSURLSession *)session 
          dataTask:(NSURLSessionDataTask *)dataTask 
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;
复制代码

上面已经介绍了什么时候会调用这个代理方法。

5.3.2 接收数据
- (void)URLSession:(NSURLSession *)session 
          dataTask:(NSURLSessionDataTask *)dataTask 
    didReceiveData:(NSData *)data;
复制代码

当我们收到数据时就会调用这个代理方法,这个方法个会被多次调用,我们需要将每次收到的数据拼接起来得到完整的数据。

5.3.3 处理缓存
- (void)URLSession:(NSURLSession *)session 
          dataTask:(NSURLSessionDataTask *)dataTask 
 willCacheResponse:(NSCachedURLResponse *)proposedResponse 
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler;
复制代码

task接收到所有期望的数据后,session会调用此代理方法。如果没有实现该方法,那么就会使用创建session时使用的configuration对象的缓存策略。

5.4 NSURLSessionDownloadDelegate

NSURLSessionDownloadDelegate继承自NSURLSessionTaskDelegate,主要用来处理下载任务。

5.4.1 处理下载任务的生命周期
- (void)URLSession:(NSURLSession *)session 
      downloadTask:(NSURLSessionDownloadTask *)downloadTask 
didFinishDownloadingToURL:(NSURL *)location;
复制代码

当下载任务完成后会调用这个代理方法,这个方法是必现实现的。location是下载文件保存的路径,要注意的是我们必现在这个方法里面将下载的文件保存到我们想要保存的目录去,因为这里返回的是一个临时路径,这个方法结束后这个路径下的文件就会被删除。另外,如果读取文件内容的话,需要在子线程执行,否则可能会造成页面卡死。

5.4.2 恢复下载
- (void)URLSession:(NSURLSession *)session 
      downloadTask:(NSURLSessionDownloadTask *)downloadTask 
 didResumeAtOffset:(int64_t)fileOffset 
expectedTotalBytes:(int64_t)expectedTotalBytes;
复制代码

当下载被取消或者下载失败后重新恢复下载时会调用这个代理方法。注意fileOffset这个参数,如果文件缓存策略或者文件最后更新日期阻止重用已经下载的文件内容,那么该值为0。否则,该值表示当前已经下载data的偏移量,那就会从这个偏移量开始下载,已经下载的不会重新下载。

恢复下载时通过downloadTaskWithResumeData:这个方法个来创建任务,那这个resumeData从哪里获取呢?前面讲过一种情况,就是当用户手动取消下载时可以将返回的resumeData存入沙盒,恢复下载时再从沙盒去获取。

下面我们来介绍一下另一种情况,当下载失败时会调用-URLSession: task: didCompleteWithError:这个代理方法,这个方法的error信息里面是携带了resumeData的,可以从errorUserInfo字典中通过NSURLSessionDownloadTaskResumeData这个键来获取resumeData

5.4.3 接收进度更新
- (void)URLSession:(NSURLSession *)session 
      downloadTask:(NSURLSessionDownloadTask *)downloadTask 
      didWriteData:(int64_t)bytesWritten 
 totalBytesWritten:(int64_t)totalBytesWritten 
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
复制代码

下载任务执行过程中周期性调用这个代理方法来通知下载进度。

  • bytesWritten表示从上次调用这个方法开始到这次调用这个方法个期间接收到的数据字节数。
  • totalBytesWritten表示已经接收到的总字节数。
  • totalBytesExpectedToWrite表示要下载的数据总长度。
文章分类
iOS
文章标签