美文网首页
NSURLSession学习整理

NSURLSession学习整理

作者: Arthur澪 | 来源:发表于2020-02-26 15:21 被阅读0次

前言

苹果在 iOS9 之后已经放弃了NSURLConnection,使用的是 iOS7 之后推出的 NSURLSession。

NSURLSession 有如下优势:

1.NSURLSession 支持 http2.0 协议
2.在处理下载任务的时候,可以直接把数据下载到磁盘(通过配置)
3.支持后台下载|上传(通过配置)
4.同一个 session 发送多个请求,只需建立一次连接(复用了TCP)
5.提供了全局的 session 并且可以统一配置,使用更加方便
6.下载的时候是多线程异步处理,效率更高

很多人都用过AFNetWorkingSDWebImage,其实底层就是封装了NSURLSession来请求任务。现在学习一下。

AFNetWorking 可以自动将服务端返回的 JSON 数据识别并解析出来,而使用 NSURLSession 则需要自己来完成。

NSURLSession简介

NSURLSession 提供了与各种协议(如HTTP、HTTPS),以及进行交互的API。NSURLSession类对象(会话对象)就是用于管理这种交互过程。它是一个高度可配置的容器,通过使用其提供的API,可进行管理控制。

简单使用,实现基本的网络请求。

NSURL *url = [NSURL URLWithString:@"http://....."];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url  completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
   // handle  response
}];
[task resume];

所有的NSURLSession 网络请求基本遵循以下几个步骤:
获取NSURLSession对象,通过session对象创建 task 任务,执行 task(task 默认是挂起的,通过resume执行)

NSURLSession工作模式

NSURLSession有3种工作模式,不同的工作模式解决不同的网络请求场景。

  • 默认会话模式(default)
    当你需要保存会话相关的caches、证书、cookies等是就使用default
  • 瞬时会话模式(ephemeral)
    所有和会话相关的caches、证书、cookies等都被缓存在RAM中,当会话销毁,缓存的数据会被自动清空。(可实现私密浏览)
  • 后台会话模式(background)
    需要在后台进行上传下载就用background。它会返回一个后台 session。可在应用程序挂起、退出或崩溃情况下,运行上传和下载任务。

上述工作模式由NSURLSessionConfiguration决定的。其三种创建方式如下:

+ (NSURLSessionConfiguration *)defaultSessionConfiguration;  
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;  
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier; 

NSURLSessionConfiguration提供了很多属性,从 指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,可以找到几乎任何你想要进行配置的选项。

关于缓存:
default模式,可以将缓存存储在磁盘上。路径是沙盒路径下Library/Caches/bundid/Cache.db。还有一个常见的设置就是cachePolicy(缓存策略),它决定要不要从缓存中获取,比如request.cachePolicy = NSURLRequestUseProtocolCachePolicy

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    //对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
    NSURLRequestUseProtocolCachePolicy = 0,

    //数据需要从原始地址加载。不使用现有缓存。
    NSURLRequestReloadIgnoringLocalCacheData = 1,

    // 不仅忽略本地缓存,同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, 

    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,

    //无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据。
    NSURLRequestReturnCacheDataElseLoad = 2,

    //无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
    NSURLRequestReturnCacheDataDontLoad = 3,

    //从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
    NSURLRequestReloadRevalidatingCacheData = 5, 
};

NSURLSession创建

提供了3种创建方法:

1、使用共享的会话,该会话使用全局的Cache,Cookie和证书。

+ (NSURLSession *)sharedSession;  

2、通过NSURLSessionConfiguration配置来创建会话对象。

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  

3、通过设置NSURLSessionConfiguration配置、代理、队列来创建会话对象。

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue; 

NSURLSessionTask

使用NSURLSession的是为了完成网络请求任务。

任务由NSURLSessionTask这个抽象类封装,其子类封装了程序三个最基本的网络任务:获取数据(如 JSON 或者 XML),上传文件,下载文件。

NSURLSessionDataTask可以用来处理一般的网络请求如 GET 等。NSURLSessionUploadTask用于处理上传请求。NSURLSessionDownloadTask主要用于处理下载请求。

下面详细介绍下三种任务:

1. NSURLSessionDataTask

任务创建,可通过一个NSURLRequestNSURL创建。

//这两个方法需要设置代理来接收数据
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

//这两个方法在completionHandler来接收数据
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

请求任务完成时,它会带有相关联的数据,可通过代理,或Block方式处理回调数据。

(1)代理方式

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDelegate,NSURLSessionTaskDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self sendRequest];
}
- (void)sendRequest{
    //创建请求
    NSURL *url = [NSURL URLWithString:@"http://httpbin.org/get"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //设置request的缓存策略(决定该request是否要从缓存中获取)
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    //创建配置(决定要不要将数据和响应缓存在磁盘)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    //创建会话
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    //生成任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    //创建的task是停止状态,需要我们去启动
    [task resume];
}
//1.接收到服务器响应的时候调用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
    NSLog(@"接收响应");
    //必须告诉系统是否接收服务器返回的数据
    //默认是completionHandler(NSURLSessionResponseAllow)
    //可以再这边通过响应的statusCode来判断否接收服务器返回的数据
    completionHandler(NSURLSessionResponseAllow);
}
//2.接受到服务器返回数据的时候调用,可能被调用多次
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    NSLog(@"接收到数据");
    //一般在这边进行数据的拼接,在方法3才将完整数据回调
//    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
//3.请求完成或者是失败的时候调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
    NSLog(@"请求完成或者是失败");
    //在这边进行完整数据的解析,回调
}
//4.将要缓存响应的时候调用(必须是默认会话模式,GET请求才可以)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler{
    //可以在这边更改是否缓存,默认的话是completionHandler(proposedResponse)
    //不想缓存的话可以设置completionHandler(nil)
    completionHandler(proposedResponse);
}
@end

(2)Block方式(便捷、常用)

NSURL *url = [NSURL URLWithString:@"http://www.connect.com/login"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"username=Tom&pwd=123" dataUsingEncoding:NSUTF8StringEncoding];

//使用全局的会话
NSURLSession *session = [NSURLSession sharedSession];
// 通过request初始化task
NSURLSessionTask *task = [session dataTaskWithRequest:request
                                   completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
    NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
 }];
// 启动
[task resume];

2. NSURLSessionUploadTask

继承自NSURLSessionDataTask。UploadTask只不过在Http请求的时候,把数据放到Http Body中。
NSURLSessionUploadTask通过request创建,在上传时指定文件源或数据源。

/=========代理方式===========/
//通过文件url来上传
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;  
//通过文件data来上传
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;  
//通过文件流来上传
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;

/=========Block方式===========/
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  

3种创建的区别:
NSData:如果对象已经在内存里
File:如果对象在磁盘上,这样做有助于降低内存使用
Stream:通过流对象,可以不用一次性将所有的流数据加载到内存中
不过使用Stream一定要实现URLSession:task:needNewBodyStream:,因为Session没办法在重新尝试发送Stream的时候找到数据源。

基本上传任务的使用:
Uploadtask 的创建需要使用一个 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];

上传图片的例子

- (void)uploadRequest{
    //创建请求
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.freeimagehosting.net/upload.php"]];
    //如果是上传文字就是@"application/json"
    [request addValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
    [request addValue:@"text/html" forHTTPHeaderField:@"Accept"];
    [request setHTTPMethod:@"POST"];
    [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
    [request setTimeoutInterval:20];
    NSData * imagedata = UIImageJPEGRepresentation([UIImage imageNamed:@"person"],1.0);
    
    //创建配置(决定要不要将数据和响应缓存在磁盘)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //创建会话
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    
    NSURLSessionUploadTask * uploadtask = [session uploadTaskWithRequest:request fromData:imagedata completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        //发送完成的回调
        
    }];
    [uploadtask resume];
}
//发送数据过程中会执行(执行多次)
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    NSLog(@"发送数据中");
    //在这边监听发送的进度
    //progress = totalBytesSent/(float)totalBytesExpectedToSend
}

3. NSURLSessionDownloadTask

下载文件可以实现断点下载
内部已经实现了数据边接收、边写入沙盒的操作(直接下载到磁盘)
支持BackgroundSession(后台下载)

Download task也需要一个request或者Url,不同之处在于 completionHandler这个 block。它是将数据一点点地写入本地的临时文件。所以在 completionHandler 里,需要把文件从一个临时地址,移动到一个永久的地址保存起来。而Datataskuploadtask会在任务完成时一次性返回。

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];

普通下载过程:
(代理方式,需遵守NSURLSessionDelegate,NSURLSessionDownloadDelegate

- (void)downloadRequest{
    //创建请求
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/image/jpeg"]];
    
    //创建配置(决定要不要将数据和响应缓存在磁盘)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //创建会话
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    
    NSURLSessionDownloadTask * downloadtask = [session downloadTaskWithRequest:request];
    [downloadtask resume];
}

//  代理回调方法
//1. downloadTask下载过程中会执行
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    NSLog(@"下载中...");
    NSLog(@"写入数据大小%lld,总写入数据大小%lld,总期望数据大小%lld",bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
    //监听下载的进度
}
//2.downloadTask下载完成的时候会执行
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
    NSLog(@"下载完成");
       //该方法内部已经完成了边接收数据边写沙盒的操作,解决了内存飙升的问题
    //对数据进行使用,或者保存(默认存储到临时文件夹 tmp 中,需要剪切文件到 cache)
    
    //保存
    NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];

    //使用
    NSData * data = [NSData dataWithContentsOfURL:location.filePathURL];
    UIImage * image = [UIImage imageWithData:data];
    UIImageWriteToSavedPhotosAlbum(image, nil,nil,nil);
}
//3.请求完成或者是失败的时候调用(Session层次的Task完成的事件)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
    NSLog(@"请求完成或者是失败");
}

接下来了解一下,下载的API。除了通过url或request下载外,还可以通过之前已经下载的数据来创建下载任务(也就是断点续传)。同样地可以通过completionHandler指定任务完成后的回调代码块。

// 使用这种方式取消下载可以得到将来用来恢复的数据,保存起来
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
    self.resumeData = resumeData;
}];

// 由于下载失败导致的下载中断会进入此协议方法,也可以得到用来恢复的数据
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    // 保存恢复数据
    self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}

// 恢复下载时接过保存的恢复数据
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
// 启动任务
[self.task resume];

  • 断点续传的前提是,服务器也支持断点续传。

相关文章

网友评论

      本文标题:NSURLSession学习整理

      本文链接:https://www.haomeiwen.com/subject/xazpchtx.html