一、NSURLSession概览
1 NSURLSession的类型
- 默认会话(Default Sessions)使用了持久的磁盘缓存,并且将用户的证书存入钥匙串
- 临时会话(Ephemeral Sessions)没有向磁盘中存入任何数据,所有相关内容都存在RAM中,当会话无效时,证书及缓存的数据都会被清除掉。
- 后台会话(Background Sessions)除了使用了一个单独的线程来处理会话之外,与默认会话类似。但使用后台会话有一些限制,比如会话必须提供事件交付的代理方法、只有HTTP和HTTPS协议支持后台会话、总是伴随着重定向。仅仅在上传文件时才支持后台会话,当你上传二进制对象或者数据流时是不支持后台会话的。当App进入后台时,后台传输就会被初始化。(需要注意的是iOS8和OS X 10.10之前的版本中后台会话是不支持数据任务(data task)的)
会话的类型都要通过NSURLSessionConfiguration来指定
NSURLSessonConfiguration的创建
//默认会话
NSURLSessionConfiguration* defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
//临时会话
NSURLSessionConfiguration* ephemeralSessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
//后台会话
NSURLSessionConfiguration* backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"bacjgroundID"];
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
2 NSURLSession的各种任务
在一个Session会话中可以发起的任务分为三种:数据任务(Data task),下载任务(Download task),上传任务(Upload task)。
- Data Task(数据任务)负责使用NSData对象来发送和接收数据。Data Task是为了那些简短的并且经常从服务器请求的数据而准备的。该任务可以没请求一次就对返回的数据进行一次处理。
- Download task(下载任务)以表单的形式接收一个文件的数据,该任务支持后台下载。
- Upload task(上传任务)以表单的形式上传一个文件的数据,该任务同样支持后台下载。
二.RUL编码
1 概述
1.URL编码概述
无论是GET、POST还是其他的请求,与服务器交互的URL是需要进行编码的。因为进行URL编码的参数服务器那边才能进行解析,为了能和服务器正常的交互,我们需要对我们的参数进行转义和编码。先简单的聊一下什么是URL吧,其实URL是URI(Uniform Resource Identifier ---- 统一资源定位符)的一种。URL就是互联网上资源的地址,用户可以通过URL来找到其想访问的资源。RFC3986文档规定,<font color = red>Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符,</font>如果你的URL中含有汉字,那么就需要对其进行转码了。<font color = red>RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]。</font>
在URL编码时有一定的规则,在Get请求中Query是存放在URL后边,而在POST中是放在Request的Body中。如果你的参数只是一个<font color = red>key-Value</font>, 那么Query的形式就是key = value。如果你的参数是一个数组比如<font color = red>key = [itme1, item2, item3,……]</font>,那么你的Query的格式就是<font color = red>key[]=item1&key[itme2]&key[item3]……</font>。如果你的参数是一个字典比如<font color = red>key = ["subKey1":"item1", "subKey2":"item2"]</font>, 那么Query对应的形式就是<font color = red>key[subKey1]=item1&key[subKey2]=item2</font>.接下来我们要做的就是将字典进行URL编码。
2.编码
AFNetworking中对应的编码如下:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
三、数据任务--NSURLSesionDataTask
NSURLSessionDataTask的使用步骤
- 首先我们先创建会话使用的URL,在创建URL是我们要对parameters字典参数进行URL编码。如果是GET方式的请求的话就使用?号将我们编码后的字符串拼接到URL后方即可。
- 然后创建我们会话使用的请求(NSURLMutableRequest),在创建请求时我们要指定请求方式是POST还是GET。如果是POST方式,我们就将编码后的URL字符串放入request的HTTPBody中即可,有一点需要注意的是我们传输的数据都是二进制的,所以在将字符串存入HTTPBody之前要将其转换成二进制,在转换成二进制的同时我们使用的是UTF8这种编码格式。
- 创建完Request后,我们就该创建URLSession了,此处我们为了简单就获取了全局的Session单例。我们使用这个Session单例创建了含有Request对象的一个DataTask。在这个DataTask创建时,有一个尾随闭包,这个尾随闭包用来接收服务器返回来的数据。当然此处可以指定代理,使用代理来接收和解析数据的,稍后会介绍到。
- 最后切记创建好的Data Task是处于挂起状态的,需要你去唤醒它,所以我们要调用dataTask的resume方法进行唤醒。具体如下所示。
NSString* baseUrlStr = [NSString stringWithUTF8String:"www.baidu.com"];
NSURL* url = [NSURL URLWithString:baseUrlStr];
NSMutableURLRequest* mutableRequest = [NSMutableURLRequest requestWithURL:url];
//设置请求方式
mutableRequest.HTTPMethod = @"GET";
//参数编码
//拼接参数
//get 用 ?
//post httpbody
//创建data task
NSURLSessionDataTask* dataTask = [self.session dataTaskWithRequest:mutableRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
//唤起
[dataTask resume];
四、上传任务 --- Upload Task
NSURLSessionUploadTask的使用步骤:
- 先创建URL和request并为request指定请求方式为POST
- 然后创建Session,此处我们使用SessionConfiguration为Session的类型指定为default session类型。并为该Session指定代理对象为self。
- 最后使用Session来创建upload task,在创建upload task时为上传任务指定NSURLRequest对象,并且传入要上传的表单数据formData,当然不要忘了将任务进行唤醒。
NSString* baseUrlStr = [NSString stringWithUTF8String:"www.baidu.com"];
NSURL* url = [NSURL URLWithString:baseUrlStr];
NSMutableURLRequest* mutableRequest = [NSMutableURLRequest requestWithURL:url];
//设置请求方式
mutableRequest.HTTPMethod = @"POST";
//参数编码
//拼接参数
//post httpbody
//上传文件的数据
NSString* uploadStr = @"this is a upload str";
NSData* uploadData = [uploadStr dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithRequest:mutableRequest fromData:uploadData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
//唤醒
[uploadTask resume];
备注:
在上传文件时,如果你想时刻的监听上传的进度,你可以去实现NSURLSessionTaskDelegate中的didSendBodyData方法,该方法会实时的监听文件上传的速度。
bytesSent回调参数表示本次上传的字节数,
totalBytesSend回调参数表示已经上传的数据大小,totalBytesExpectedToSend表示文件公有的大小。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
五、下载任务--Download Task
1.创建后台会话
NSURLSessionConfiguration* backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"bacjgroundID"];
self.session = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:queue];
2.开始下载
NSURLSessionConfiguration* backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"bacjgroundID"];
self.session = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:nil];
if (self.resumeData) {
//如果有已下载的数据
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//Block下载方式不适合大文件下载
//接收数据
//确定文件路径
//剪切文件
}];
}else{
//没下载过
NSString* baseUrlStr = [NSString stringWithUTF8String:"www.baidu.com"];
NSURL* url = [NSURL URLWithString:baseUrlStr];
NSMutableURLRequest* mutableRequest = [NSMutableURLRequest requestWithURL:url];
//设置请求方式
mutableRequest.HTTPMethod = @"POST";
//参数编码
//拼接参数
//post httpbody
//上传文件的数据
NSString* uploadStr = @"this is a upload str";
NSData* uploadData = [uploadStr dataUsingEncoding:NSUTF8StringEncoding];
self.downloadTask = [self.session downloadTaskWithRequest:mutableRequest completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//Block下载方式不适合大文件下载
//接收数据
//确定文件路径
//剪切文件
}];
}
[self.downloadTask resume];
推荐使用代理方法来处理下载
3.下载的暂停与恢复
//取消下载(不可以恢复)
- (void)cancleDownLoad
{
[self.downloadTask cancel];
}
//可以恢复下载
- (void)cancleByResumeData
{
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
self.resumeData = resumeData;
}];
}
//挂起下载
- (void)suspendDownLoad
{
[self.downloadTask suspend];
}
//回复继续下载
- (void)resumeDownLoad
{
[self.downloadTask resume];
}
4.下载任务的回调
NSURLSessionDownloadDelegate中的方法
/**
下载完成
@param session session对象
@param downloadTask downloadTask对象
@param location 临时文件的下载目录
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
//临时文件在下载完成之后如果你不做任何处理的话,那么就会被自动删除
//创建文件存储路径
//移动临时文件
}
/**
下载文件偏移,主要用于断点续传
@param session session对象
@param downloadTask downloadTask对象
@param fileOffset 已经下载文件的大小
@param expectedTotalBytes 文件的总大小
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
//更新下载进度条
}
/**
实时监听下载任务回调
@param session session对象
@param downloadTask downloadTask对象
@param bytesWritten 本次下载的数据
@param totalBytesWritten 已经下载的数据总量
@param totalBytesExpectedToWrite 文件的总量
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//更新下载进度条
}
//后台下载完成之后的操作
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
//配合application:handleEventsForBackgroundURLSession:completionHandler:方法使用
}
//当NSURLSession在后台开启几个任务之后, 如果有其他几个任务完成后系统会调用应用程序的
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
}
NSURLSessionTaskDelegate中的方法
//任务完成 不管是不是下载都会调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
}
六、网络缓存
网络缓存在网络请求中使用的还是蛮多的,尤其是加载一些H5页面时经常会加一些缓存来提高用户体验。有时的一些数据也会进行缓存,你可将数据缓存到你的SQLite数据库、PList文件,或者直接使用NSURLSession相关的东西进行缓存。
1、缓存策略
在配置网络请求缓存时,有着不同的请求缓存策略。下方就是所有支持的网络缓存策略:
- NSURLRequestUseProtocolCachePolicy -- 缓存存在就读缓存,若不存在就请求服务器
- NSURLRequestReloadIgnoringLocalCacheData -- 忽略缓存,直接请求服务器数据
- NSURLRequestReturnCacheDataElseLoad -- 本地如有缓存就使用,忽略其有效性,无则请求服务器
- NSURLRequestReturnCacheDataDontLoad -- 直接加载本地缓存,没有也不请求网络
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData -- 尚未实现
- NSURLRequestReloadRevalidatingCacheData -- 尚未实现
2、NSMutableURLRequest指定缓存策略
mutableRequest.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData
缓存目录默认为~/Library/Caches/[Boundle ID]/fsCachedData/缓存文件
3、NSURLSessionConfiguration指定缓存策略
SessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
4、NSURLCache + request进行缓存
NSInteger memoryCapacity = 4 * 1024 * 1024;
NSInteger diskCapacity = 10 * 1024 * 1024;
NSString* cacheFilePath = @"myCache";
NSURLCache* urlCache = [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:cacheFilePath];
[NSURLCache setSharedURLCache:urlCache];
有一点需要注意的是此处设置的缓存路径是相对于/Library/Caches/[Boundle ID]/的
5、NSURLCache +SessionConfiguration进行缓存
SessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
NSInteger memoryCapacity = 4 * 1024 * 1024;
NSInteger diskCapacity = 10 * 1024 * 1024;
NSString* cacheFilePath = @"myCache";
NSURLCache* urlCache = [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:cacheFilePath];
SessionConfiguration.URLCache = urlCache;
6、清除缓存
NSFileManager removeItemAtPath
七、请求认证
1.认证方式
- NSURLAuthenticationMethodHTTPBasic: HTTP基本认证,需要提供用户名和密码
- NSURLAuthenticationMethodHTTPDigest: HTTP数字认证,与基本认证相似需要用户名和密码
- NSURLAuthenticationMethodHTMLForm: HTML表单认证,需要提供用户名和密码
- NSURLAuthenticationMethodNTLM: NTLM认证,NTLM(NT LAN Manager)是一系列旨向用户提供认证,完整性和机密性的微软安全协议
- NSURLAuthenticationMethodNegotiate: 协商认证
- NSURLAuthenticationMethodClientCertificate: 客户端认证,需要客户端提供认证所需的证书
- NSURLAuthenticationMethodServerTrust: 服务端认证,由认证请求的保护空间提供信任
后两个就是我们在请求HTTPS时会遇到的认证,需要服务器或者客户端来提供认证的,这个证书就是我们平时常说的CA证书.
2.认证处理策略
当我们进行网络求时,会对相应的认证做出响应。在NSURLSession进行网络请求时支持四种证书处理策略,这些认证处理策略以枚举的形式来存储,枚举的类型为NSURLSessionAuthChallengeDisposition。下方就是认证的所有处理策略:
- NSURLSessionAuthChallengeUseCredential 使用证书
- NSURLSessionAuthChallengePerformDefaultHandling 执行默认处理, 类似于该代理没有被实现一样,credential参数会被忽略
- NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消请求,credential参数同样会被忽略
- NSURLSessionAuthChallengeRejectProtectionSpace 拒绝保护空间,重试下一次认证,credential参数同样会被忽略
3.HTTPS请求证书处理
发起上述https请求后,就会执行下方的代理方法。下方的委托代理方法属于NSURLSessionDelegate中处理认证的方法,也就是如果服务器需要认证时就会执行下方的回调方法。下方代码首先从授权质疑的保护空间中取出认证方式,然后根据不同的认证方式进行不同的处理。下方给出了两种认证方式的处理,上面的if语句块赋值服务端认证,下面的if语句块负责HTTP的基本认证。具体处理方式如下所示。有一点需要注意的是如果在该委托回调方法中如果不执行completionHandler闭包,那么认证就会失效,是请求不到数据的。
/**
服务器需要验证时会触发
@param session session对象
@param challenge 授权质疑
@param completionHandler block
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
// 判断是否是信任服务器证书
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
//https
// 告诉服务器,客户端信任证书
// 创建凭据对象
NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 通过completionHandler告诉服务器信任证书
completionHandler(NSURLSessionAuthChallengeUseCredential,credntial);
}
/**
* HTTP BASE
*/
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
NSURLCredential *credntial = [[NSURLCredential alloc] initWithUser:@"username" password:@"password" persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential,credntial);
}
//取消请求
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
}
八、NSURLSession相关代理
1、NSURLSessionDelegate
- didReceiveChallenge
其他
//Session无效后被调用
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
{
}
//后台Session在执行完后台任务后所执行的方法
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
}
2、NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;
{
}
3、NSURLSessionDataDelegate
NSURLSessionDataDelegate中的方法主要是用来处理Data Task任务相应的事件的
1、相应处理策略
- NSURLSessionResponseCancel 取消数据的加载,默认为 Cancel。此处理方式就是忽略数据的加载,取消对响应数据的进一步解析
- NSURLSessionResponseAllow 允许继续操作, 会执行 NSURLSessionDataDelegate中的dataTaskDidReceiveData回调方法
- NSURLSessionResponseBecomeDownload 将Data Task的响应转变为DownloadTask,会执行NSURLSessionDownloadDelegate代理中相应的方法
- NSURLSessionResponseBecomeStream 将Data Task的响应转变为DownloadTask,会执行NSURLSessionStreamDelegate代理中相应的方法
2.代理方法
/**
收到响应时会执行的方法
@param session session
@param dataTask dataTask
@param response 服务器响应
@param completionHandler block
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
//处理策略设置成Allow后会执行下方的方法,如果响应处理策略不是Allow那么就不会接收到服务器的Data
}
/**
接收数据后会执行的方法
@param session session
@param dataTask dataTask
@param data 接收的数据
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
}
/**
任务转变为下载时所执行的方法
@param session session
@param dataTask dataTask
@param downloadTask downloadTask
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
}
/**
任务转变为streamTask时所执行的方法
@param session session
@param dataTask dataTask
@param streamTask streamTask
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask
{
//iOS9
}
/**
将要进行缓存时执行的方法
@param session session
@param dataTask dataTask
@param proposedResponse streamTask
@param completionHandler block
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler
{
}