NSURLSession详细解析

2020-06-12  本文已影响0人  IAM121

一 概述

NSURLConnection在iOS9被宣布弃用,NSURLSession在2013年随着iOS7的发布一起面世。

Session翻译为中文意思是会话,我们知道,在七层网络协议中有物理层->数据链路层->网络层->传输层->会话层->表示层->应用层,那我们可以将NSURLSession类理解为会话层,用于管理网络接口的创建、维护、删除等等工作,我们要做的工作也只是会话层之后的层即可,底层的工作NSURLSession已经帮我们封装好了。


Snip20200612_7.png

二 NSURLSession的使用

NSURLSession 本身是不会进行请求的,而是通过创建 task 的形式进行网络请求(resume() 方法的调用),同一个 NSURLSession 可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。NSURLSession的使用有如下几步:

第一步:创建NSURLSession对象
第二步: 使用NSURLSeesion对象创建Task
第三步: 启动任务

1.创建NSURLSession对象

(1)直接创建

NSURLSession *session = [NSURLSession sharedSession];

(2)设置加代理获得

// 使用代理方法需要设置代理,但是session的delegate属性是只读的,要想设置代理只能通过这种方式创建session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
    delegate:self
    delegateQueue:[[NSOperationQueue alloc] init]];

(3)配置后创建

[NSURLSession sessionWithConfiguration:defaultSessionConfiguration];

注意:
关于NSURLSession的配置有三种类型

//默认的配置会将缓存存储在磁盘上
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;

//瞬时会话模式不会创建持久性存储的缓存
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;

//后台会话模式允许程序在后台进行上传下载工作
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier
2. 使用NSURLSession对象创建Task

NSURLSessionTask的创建要根据具体需要创建相应类型的Task


Snip20200612_5.png
2.1 NSURLSessionDataTask

通过request对象或url创建:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

通过request对象或url创建,同时指定任务完成后通过completionHandler指定回调的代码块:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;    

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;

2.2 NSURLSessionUploadTask

通过request创建,在上传时指定文件源或数据源:

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;  
   
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;  
  
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;  

通过completionHandler指定任务完成后的回调代码块:

- (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;
2.3 NSURLSessionDownloadTask

下载任务支持断点续传,第三种方式是通过之前已经下载的数据来创建下载任务:

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;    
    
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;    
  
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

同样地可以通过completionHandler指定任务完成后的回调代码块:

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;    

- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;    

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;

我们在使用三种 task 的任意一种的时候都可以指定相应的代理。NSURLSession 的代理对象结构如下图:


Snip20200612_6.png

NSURLSessionDelegate – 作为所有代理的基类,定义了网络请求最基础的代理方法。

NSURLSessionTaskDelegate – 定义了网络请求任务相关的代理方法。

NSURLSessionDownloadDelegate – 用于下载任务相关的代理方法,比如下载进度等等。

NSURLSessionDataDelegate – 用于普通数据任务和上传任务。

3.启动任务
//启动任务
[task resume];

三 举例

1.GET 请求
/// 向网络请求数据
- (void)NSURLSessionTest {
    // 1.创建url
    // 请求一个网页
    NSString *urlString = @"https://www.jianshu.com/u/835badda6ae0";

  // 一些特殊字符编码
    urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    NSURL *url = [NSURL URLWithString:urlString];
    
    // 2.创建请求 
    NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];

    // 3.采用苹果提供的共享session
    NSURLSession *sharedSession = [NSURLSession sharedSession];
    
    // 4.由系统直接返回一个dataTask任务
    NSURLSessionDataTask *dataTask = [sharedSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 网络请求完成之后就会执行,NSURLSession自动实现多线程
        NSLog(@"%@",[NSThread currentThread]);
        if (data && (error == nil)) {
            // 网络访问成功
            NSLog(@"data=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        } else {
            // 网络访问失败
            NSLog(@"error=%@",error);
        }
    }];
    
    // 5.每一个任务默认都是挂起的,需要调用 resume 方法
    [dataTask resume];
}
2.POST 请求
//1、创建NSURLSession对象
NSURLSession *session = [NSURLSession sharedSession];

//2、利用NSURLSession创建任务(task)
NSURL *url = [NSURL URLWithString:@"http://www.jianshu.com/login"];

//创建请求对象里面包含请求体
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"username=121&pwd=123456" dataUsingEncoding:NSUTF8StringEncoding];

NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
      
    NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
    //打印解析后的json数据
    //NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);

}];

//3、执行任务
 [task resume];
3.文件的上传

我们可以使用NSURLSessionUploadTask进行文件的上传,使用NSURLSessionUploadTask文件上传共有两种方法:
方法1:

NSURLSessionUploadTask *task =
[[NSURLSession sharedSession] uploadTaskWithRequest:request
                                           fromFile:fileName
                                  completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];

方法2:

[self.session uploadTaskWithRequest:request
                            fromData:body
                   completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
 NSLog(@"-------%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
 }];

1.以数据流的方式进行上传
这种方式好处就是大小不受限制,代码如下:

- (void) NSURLSessionBinaryUploadTaskTest {
    // 1.创建url
    NSString *urlString = @"http://www.jianshu.com/upload.php";
    // urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
    NSURL *url = [NSURL URLWithString:urlString];

    // 2.创建请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 文件上传使用post
    request.HTTPMethod = @"POST";

    // 3.开始上传   request的body data将被忽略,而由fromData提供
    [[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:[NSData dataWithContentsOfFile:@"/Desktop/121/Desktop/121.jpg"]     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error == nil) {
            NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        } else {
            NSLog(@"upload error:%@",error);
        }
    }] resume];
}

2.以拼接表单的方式进行上传
2.1上传的关键是请求体部分的表单拼接,获取本地上传文件的类型(MIME Types),至于具体的网络上传则很简单。 另外拼接表单的方式会有大小限制,即HTML的MAX_FILE_SIZE限制(可以自己设定,一般2MB)。
2.2 根据上面的继承关系图,我们知道uploadTask是dataTask的子类,也可以使用uploadTask来代替dataTask。

表单拼接格式如下,boundary作为分界线:

--boundary
Content-Disposition:form-data;name=”表单控件名称”;filename=”上传文件名称”
Content-Type:要上传文件MIME Types

要上传文件二进制数据;

--boundary--

示例代码如下:

- (void)NSURLSessionUploadTaskTest {
    // 1.创建url  采用Apache本地服务器
    NSString *urlString = @"http://localhost/upload/upload.php";
    urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
    NSURL *url = [NSURL URLWithString:urlString];

    // 2.创建请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 文件上传使用post
    request.HTTPMethod = @"POST";

    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",@"boundary"];

    [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
    // test.jpg
    // 3.拼接表单,大小受MAX_FILE_SIZE限制(2MB)  FilePath:要上传的本地文件路径  formName:表单控件名称,应于服务器一致
    NSData* data = [self getHttpBodyWithFilePath:@"/Users/lifengfeng/Desktop/test.jpg" formName:@"file" reName:@"newName.png"];
    request.HTTPBody = data;
    // 根据需要是否提供,非必须,如果不提供,session会自动计算
    [request setValue:[NSString stringWithFormat:@"%lu",data.length] forHTTPHeaderField:@"Content-Length"];

    // 4.1 使用dataTask
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error == nil) {
            NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        } else {
            NSLog(@"upload error:%@",error);
        }

    }] resume];
#if 0
    // 4.2 开始上传 使用uploadTask   fromData:可有可无,会被忽略
    [[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:nil     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error == nil) {
            NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        } else {
            NSLog(@"upload error:%@",error);
        }
    }] resume];
#endif
}

/// filePath:要上传的文件路径   formName:表单控件名称  reName:上传后文件名
- (NSData *)getHttpBodyWithFilePath:(NSString *)filePath formName:(NSString *)formName reName:(NSString *)reName
{
    NSMutableData *data = [NSMutableData data];
    NSURLResponse *response = [self getLocalFileResponse:filePath];
    // 文件类型:MIMEType  文件的大小:expectedContentLength  文件名字:suggestedFilename
    NSString *fileType = response.MIMEType;

    // 如果没有传入上传后文件名称,采用本地文件名!
    if (reName == nil) {
        reName = response.suggestedFilename;
    }

    // 表单拼接
    NSMutableString *headerStrM =[NSMutableString string];
    [headerStrM appendFormat:@"--%@\r\n",@"boundary"];
    // name:表单控件名称  filename:上传文件名
    [headerStrM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",formName,reName];
    [headerStrM appendFormat:@"Content-Type: %@\r\n\r\n",fileType];
    [data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]];

    // 文件内容
    NSData *fileData = [NSData dataWithContentsOfFile:filePath];
    [data appendData:fileData];

    NSMutableString *footerStrM = [NSMutableString stringWithFormat:@"\r\n--%@--\r\n",@"boundary"];
    [data appendData:[footerStrM  dataUsingEncoding:NSUTF8StringEncoding]];
//    NSLog(@"dataStr=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    return data;
}

/// 获取响应,主要是文件类型和文件名
- (NSURLResponse *)getLocalFileResponse:(NSString *)urlString
{
    urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
    // 本地文件请求
    NSURL *url = [NSURL fileURLWithPath:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    __block NSURLResponse *localResponse = nil;
    // 使用信号量实现NSURLSession同步请求
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        localResponse = response;
        dispatch_semaphore_signal(semaphore);
    }] resume];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return  localResponse;
}

4.文件的下载

-(void)startDownLoad:(UIButton *)sender {
    //1.url
//    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    NSURL *url = [NSURL URLWithString:@"http://imgcache.qq.com/qzone/biz/gdt/dev/sdk/ios/release/GDT_iOS_SDK.zip"];
    
    //2.创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //3.创建session
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    //4.创建Task
    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
    
    //5.执行Task
    [downloadTask resume];
    
    self.downloadTask = downloadTask;
}


#pragma mark ---------------------- 代理方法
#pragma mark NSURLSessionDownloadDelegate
/**
 *  写数据
 *
 *  @param session                   会话对象
 *  @param 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
{
    //1. 获得文件的下载进度
    NSLog(@"%f",1.0 * totalBytesWritten/totalBytesExpectedToWrite);
}

/**
 *  当恢复下载的时候调用该方法
 *
 *  @param fileOffset         从什么地方下载
 *  @param expectedTotalBytes 文件的总大小
 */
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"%s",__func__);
}

/**
 *  当下载完成的时候调用
 *
 *  @param location     文件的临时存储路径
 */
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"%@",location);
    
    //1 拼接文件全路径
    NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
    //2 剪切文件
    [[NSFileManager defaultManager]moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
    NSLog(@"%@",fullPath);
}

/**
 *  请求结束 或者取消请求的时候都会去调用该方法
 */
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"didCompleteWithError");
}

//暂停操作
- (void)pauseDownLoad:(UIButton *)sender {
    NSLog(@"+++++++++++++++++++暂停");
    [self.downloadTask suspend];
}
//取消操作
- (void)cancelDownLoad:(UIButton *)sender {
    NSLog(@"+++++++++++++++++++取消");
    //[self.downloadTask cancel];
    
    //恢复下载的数据!=文件数据
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        self.resumData = resumeData;
    }];
}

//恢复下载
- (void)resumDownLoad:(UIButton *)sender {
    NSLog(@"+++++++++++++++++++恢复下载");
    if(self.resumData)
    {
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumData];
    }
    
    [self.downloadTask resume];
}


上一篇下一篇

猜你喜欢

热点阅读