美文网首页
NSURLSession详细解析

NSURLSession详细解析

作者: IAM121 | 来源:发表于2020-06-12 23:06 被阅读0次

    一 概述

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

    相关文章

      网友评论

          本文标题:NSURLSession详细解析

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