美文网首页iOS干货ios开发笔记锻炼吃饭的家伙
iOS学习笔记13-网络(二)NSURLSession

iOS学习笔记13-网络(二)NSURLSession

作者: 执着丶执念 | 来源:发表于2016-03-31 13:24 被阅读1487次

    在2013年WWDC上苹果揭开了NSURLSession的面纱,将它作为NSURLConnection的继任者。现在使用最广泛的第三方网络框架:AFNetworking、SDWebImage等等都使用了NSURLSession。作为iOS开发人员,应该紧随苹果的步伐,不断的学习,无论是软件的更新、系统的更新、API的更新,而不能墨守成规。

    • 相比较NSURLConnection,NSURLSession提供了 配置会话缓存、协议、cookie和证书能力,这使得网络架构和应用程序可以独立工作、互不干扰。
    • 另外,NSURLSession另一个重要的部分是 会话任务,它负责加载数据,在客户端和服务器端进行文件的上传下载。

    下面让我们正式进入NSURLSession学习。

    一、NSURLSession介绍

    NSURLSession类结构图
    在NSURLSession时代,网络请求基本上由3个任务完成:
    • NSURLSessionData:请求数据任务
    • NSURLSessionUploadTask:请求上传任务
    • NSURLSessionDownloadTask:请求下载任务
    关系图如下:
    NSURLSessionTask关系图

    NSURLSessionTask支持任务的暂停、取消和恢复,并且默认任务运行在其他非主线程中

    二、NSURLSession使用

    说了这么多,是时候来露两手了,具体NSURLSession怎么用呢?

    1. 数据请求

    先看一个网络数据请求实例,和上一章的NSURLConnection请求对比参考:
    - (void)loadJsonData{
        //1.创建url
        NSString *urlStr = @"http://192.168.1.208/ViewStatus.aspx?userName=KenshinCui&password=123";
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlStr];
        //2.创建请求
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        //3.创建会话(这里使用了一个全局会话)
        NSURLSession *session = [NSURLSession sharedSession];
        //4.通过会话创建任务
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request 
                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (!error) {
                NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"%@",dataStr);
            }else{
                NSLog(@"error is :%@",error.localizedDescription);
            }
        }];
        //5.每一个任务默认都是挂起的,需要调用 resume 方法启动任务
        [dataTask resume];
    }
    

    不难发现NSURLSession网络请求的五步走黄金油战略

    1. 创建NSURL
    1. 创建NSURLRequest
    2. 创建会话NSURLSession
    3. 通过会话创建任务NSURLSessionTask的子类
    4. 调用resume方法,启动任务

    2. 文件下载

    文件下载也是一样的,只是换上下载任务NSURLSessionDownloadTask就行,对回调做不同处理,一切都要贯彻五步走战略,O(∩_∩)O哈!

    常用的创建文件下载任务的方法如下:
    /* 回调类型,这是我为了排版方便抽出来的,实际框架中没有 */
    typedef void (^downloadCompletionBlock)(NSURL*,NSURLReponse*,NSError*);
    /* 创建文件下载任务,需要请求NSURLRequest */
    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request 
                                        completionHandler:(downloadCompletionBlock)completion;
    /* 创建文件任务,简化了一些操作,只需要URL就能进行文件下载 */
    - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url 
                                    completionHandler:(downloadCompletionBlock)completion;
    
    下面是下载实例
    -(void)downloadFile{
        //1.创建url
        NSString *fileName = @"1.jpg";
        NSString *urlStr = [NSString stringWithFormat: @"http://192.168.1.208/FileDownload.aspx?file=%@",fileName];
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlStr];
        //2.创建请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        //3.创建会话(这里使用了一个全局会话)
        NSURLSession *session = [NSURLSession sharedSession];
        //4.创建文件下载任务
        NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request 
              completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
            if (!error) {
                //注意location是下载后的临时保存路径,需要将它移动到需要保存的位置
                NSError *saveError;
                NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
                NSString *savePath = [cachePath stringByAppendingPathComponent:fileName];
                NSURL *saveUrl = [NSURL fileURLWithPath:savePath];
                [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&saveError];
                if (!saveError) {
                    NSLog(@"save sucess.");
                }
            }
        }];
        //5.启动任务
        [downloadTask resume];
    }
    
    • 回调中的location是下载后的临时保存路径,需要将它移动到需要保存的位置
    • NSFileManager的对象方法
    //将fromURL路径下的文件拷贝到toURL路径下
    - (void)copyItemAtURL:(NSURL *)fromUrl 
                     toURL:(NSURL *)toUrl 
                     error:(NSError **)error;
    

    3.文件上传

    使用NSURLConnection的文件上传时,我们还需要自己构建上传请求,主要是拼接上传表单,这是个十分麻烦的过程。
    现在使用NSURLSessionUploadTask文件上传任务,我们就可以解放了,简单粗暴。
    \(o)/~

    下面是常用的创建上传任务的方法:
    /* 回调类型,这是我为了排版方便抽出来的,实际框架中没有 */
    typedef void (^UploadCompletionBlock)(NSData*,NSURLReponse*,NSError*);
    /* 创建上传任务,需要提供上传文件二进制数据 */
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request 
                                             fromData:(NSData *)bodyData 
                                    completionHandler:(UploadCompletionBlock)completion;
    /* 创建上传任务,需要提供上传文件所在的URL路径,不过这个方法常配合“PUT”请求使用 */
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request 
                                             fromFile:(NSURL *)fillURL 
                                    completionHandler:(UploadCompletionBlock)completion;
    
    下面是上传实例:
    - (void) NSURLSessionBinaryUploadTaskTest {
        // 1.创建url,采用Apache本地服务器进行测试
        NSString *urlStr = @"http://localhost/upload.php";
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlStr];
        // 2.创建请求,这里要设置POST请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        request.HTTPMethod = @"POST";// 文件上传使用post
        // 3.获取全局会话Session
        NSURLSession *session = [NSURLSession sharedSession];
        // 4.创建上传任务,Request的Body Data将被忽略,而由fromData提供
        NSData *data = [NSData dataWithContentsOfFile:@"/Users/userName/Desktop/IMG_0359.jpg"];
        NSURLSessionUploadTask *upload =
               [session uploadTaskWithRequest:request 
                                     fromData:data     
                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error == nil) {
                NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"upload success:%@",result);
            } else {
                NSLog(@"upload error:%@",error);
            }
        }]
        // 5.启动任务
        [upload resume];
    }
    

    是不是很简单,数据请求、文件下载、文件上传基本上都差不多,使用起来比NSURLConnection方便多了,还有什么理由不用NSURLSession呢!!

    4.用dataTask上传文件【闲得蛋疼可以试一下】

    除了上面的上传方式,实际上你也可以用NSURLSessionDataTask的方式上传,不过你就要自己设置上传BodyData和Header了,具体构建细节可以参考iOS学习笔记12-网络请求(一)NSURLConnection里面的构建过程,这里给个参考吧:

    #pragma mark 上传文件
    -(void)uploadFile{
        NSString *fileName = @"pic.jpg";
        //1.创建url
        NSString *urlStr = @"http://192.168.1.208/FileUpload.aspx";
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlStr];
        //2.创建请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        request.HTTPMethod = @"POST";
        //3.构建上传表单数据
        //设置数据体
        NSData *data = [self getHttpBody:fileName];
        request.HTTPBody = data;
        //设置请求头
        NSString *lengthStr = [NSString stringWithFormat:@"%lu",(unsigned long)data.length];
        [request setValue:lengthStr forHTTPHeaderField:@"Content-Length"];
        NSString *typeStr = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBOUNDARY_STRING];
        [request setValue:typeStr forHTTPHeaderField:@"Content-Type"];
        //4.创建会话
        NSURLSession *session = [NSURLSession sharedSession];
        //5.创建dataTask任务,去做上传的功能
        NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:request 
                                     completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (!error) {
                NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"%@",dataStr);
            }else{
                NSLog(@"error is :%@",error.localizedDescription);
            }
        }];
        //6.启动任务
        [uploadTask resume];
    }
    
    上面的获取数据体方法getHttpBody,我也贴过来了
    #pragma mark 取得数据体
    -(NSData *)getHttpBody:(NSString *)fileName{
        NSMutableData *dataM = [NSMutableData data];
        NSString *type = [self getMIMETypes:fileName];
        //构建请求体body的顶部
        NSMutableString *bodyTop = [NSMutableString string];
        //宏kBOUNDARY_STRING就是boundary标示
        [bodyTop appendFormat:@"--%@\n",kBOUNDARY_STRING];
        [bodyTop appendFormat:@"Content-Disposition: form-data; name=\"file1\"; filename=\"%@\"\n",fileName];
        [bodyTop appendFormat:@"Content-Type: %@\n\n",type];
        //构建请求体body的底部
        NSString *bodyBottom = [NSString stringWithFormat:@"\n--%@--",kBOUNDARY_STRING];
        NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
        //构建请求体body中间的二进制上传数据
        NSData *fileData = [NSData dataWithContentsOfFile:filePath];
        //把顶部、数据、底部组合起来,形成body
        [dataM appendData:[bodyTop dataUsingEncoding:NSUTF8StringEncoding]];
        [dataM appendData:fileData];
        [dataM appendData:[bodyBottom dataUsingEncoding:NSUTF8StringEncoding]];
        return dataM;
    }
    

    三、会话Session控制

    上面我们都是使用的全局NSURLSession,一般情况下我们就够用,但如果遇到两个连接使用不同的资源配置的情况,怎么办?答案就是自己定制。

    • NSURLSession支持我们自己定制NSURLSession
    • NSURLSession支持的三种会话配置:
    1. defaultSessionConfiguration
      进程内会话(默认会话),用硬盘来缓存数据。
    2. ephemeralSessionConfiguration
      临时的进程内会话(内存),不会将cookie、缓存储存到本地,只会放到内存中,当应用程序退出后数据也会消失。
    3. backgroundSessionConfiguration
      后台会话,相比默认会话,该会话会在后台开启一个线程进行网络数据处理。
    下面就是定制NSURLSession的过程:
    //使用默认会话配置
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    sessionConfig.timeoutIntervalForRequest = 5.0f;//请求超时时间
    sessionConfig.allowsCellularAccess = true;//是否允许蜂窝网络下载(2G/3G/4G)
    //创建会话,指定配置和代理
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig 
                                                          delegate:self 
                                                     delegateQueue:nil];
    
    上面设置了代理,NSURLSession有很多代理协议:
    • NSURLSessionDelegateNSObject
      会话父协议
    • NSURLSessionTaskDelegateNSURLSessionDelegate
      任务协议
    • NSURLSessionDataDelegateNSURLSessionTaskDelegate
      数据协议
    • NSURLSessionDownloadDelegateNSURLSessionTaskDelegate
      下载协议
    • NSURLSessionStreamDelegateNSURLSessionTaskDelegate
      网络流协议
    下面就拿最常用的下载协议NSURLSessionDownloadDelegate来讲下:
    /* 下载中(会多次调用,可以记录下载进度) */
    - (void)URLSession:(NSURLSession *)session 
                   downloadTask:(NSURLSessionDownloadTask *)downloadTask /* 下载任务 */
                   didWriteData:(int64_t)bytesWritten /* 这次下载完成的字节数 */
              totalBytesWritten:(int64_t)totalBytesWritten /* 已经下载完成的总字节数 */
      totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; /* 需要下载完成的总字节数 */
    
    /* 成功下载完成 */
    -(void)URLSession:(NSURLSession *)session 
                     downloadTask:(NSURLSessionDownloadTask *)downloadTask /* 下载任务 */
        didFinishDownloadingToURL:(NSURL *)location;/* 下载完成后临时存放的URL */
     
    /* 任务完成,不管是否下载成功 */
    -(void)URLSession:(NSURLSession *)session 
                        task:(NSURLSessionTask *)task /* 下载任务 */
        didCompleteWithError:(NSError *)error;/* 错误 */
    
    实际上NSURLSessionTask任务除了resume启动之外,还有一些方法
    /* 取消任务 */
    - (void)cancel;
    /* 挂起任务(暂停任务) */
    - (void)suspend;
    /* 启动任务 */
    - (void)resume;
    
    下面来个代码总结:
    -(void)downloadFile{
        NSString *fileName = _textField.text;
        NSString *urlStr = [NSString stringWithFormat: @"http://192.168.1.208/FileDownload.aspx?file=%@",fileName];
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlStr];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        sessionConfig.timeoutIntervalForRequest = 5.0f;//请求超时时间
        sessionConfig.allowsCellularAccess = true;//是否允许蜂窝网络下载(2G/3G/4G)
        NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig 
                                                              delegate:self 
                                                         delegateQueue:nil];
        _downloadTask = [session downloadTaskWithRequest:request];
        [_downloadTask resume];
    }
    
    #pragma mark 点击取消下载
    -(void)cancelDownload{
        [_downloadTask cancel];
    }
    #pragma mark 点击挂起下载
    -(void)suspendDownload{
        [_downloadTask suspend];
    }
    #pragma mark 点击恢复下载
    -(void)resumeDownload{
        [_downloadTask resume];
    }
    #pragma mark - 下载任务代理
    #pragma mark 下载中(会多次调用,可以记录下载进度)
    -(void)URLSession:(NSURLSession *)session 
                    downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                    didWriteData:(int64_t)bytesWritten          
               totalBytesWritten:(int64_t)totalBytesWritten         
       totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 
    {   
        [self setUIStatus:totalBytesWritten expectedToWrite:totalBytesExpectedToWrite];//设置界面状态
    }
    #pragma mark 下载完成
    -(void)URLSession:(NSURLSession *)session 
                       downloadTask:(NSURLSessionDownloadTask *)downloadTask 
          didFinishDownloadingToURL:(NSURL *)location
    {
        NSError *error;
        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        NSString *savePath = [cachePath stringByAppendingPathComponent:_textField.text];
        NSURL *saveUrl = [NSURL fileURLWithPath:savePath];
        [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&error];
    }
    #pragma mark 任务完成,不管是否下载成功
    -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 
                              didCompleteWithError:(NSError *)error
    {
        [self setUIStatus:0 expectedToWrite:0];//设置界面状态
    }
    

    四、Session后台开启任务

    NSURLSession支持程序的后台下载和上传,苹果官方将其称为进程之外的上传和下载,这些任务都是交给后台守护线程完成的,而非应用程序本身。
    即使文件在下载和上传过程中崩溃了也可以继续运行(注意如果用户强制退关闭应用程序,NSURLSession会断开连接)。

    我们先来看下如何创建一个后台Session
    #pragma mark 取得一个后台会话(保证一个后台会话,这通常很有必要,这里采用单例模式的形式)
    - (NSURLSession *)backgroundSession{
        static NSURLSession *session = nil;
        static dispatch_once_t token;//下面代码块只执行一次,以后都不执行
        dispatch_once(&token, ^{
            NSStirng *identifier = @"com.cmjstudio.URLSession";
            NSURLSessionConfiguration *sessionConfig = 
                  [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
            sessionConfig.timeoutIntervalForRequest = 5.0f;//请求超时时间
            sessionConfig.discretionary = YES;//系统自动选择最佳网络下载
            sessionConfig.HTTPMaximumConnectionsPerHost = 5;//限制每次最多5个连接
            //创建会话
            session = [NSURLSession sessionWithConfiguration:sessionConfig 
                                                    delegate:self 
                                               delegateQueue:nil];
        });
        return session;
    }
    

    然后我们拿到这个后台Session就可以做上面我们讲的下载和上传任务了。

    我们来了解下程序进入后台后,任务是如何调度的,先上图:
    程序后台任务下载和上传的调度示意图

    当程序进入后台后,事实上任务是交给iOS系统来调度的,具体什么时候下载完成就不得而知,例如有个较大的文件经过一个小时下载完了,正常打开应用程序看到的此文件下载进度应该在100%的位置,但是由于程序已经在后台无法更新程序UI,而此时可以通过应用程序代理方法进行UI更新。

    在AppDelegate.m中添加以下函数:
    /* 
        有其中几个任务完成后,系统会调用此应用程序的该方法
        此方法会包含一个competionHandler,通常我们会保存此对象
        competionHandler此操作表示应用完成所有处理工作
    */
    - (void)application:(UIApplication *)application 
            handleEventsForBackgroundURLSession:(NSString *)identifier 
                              completionHandler:(void (^)())completionHandler
    {
        //backgroundSessionCompletionHandler是自定义的一个属性
        self.backgroundSessionCompletionHandler = completionHandler;
    }
    
    在XXSession.m文件中实现NSURLSessionDelegate代理方法:
    /* 
        直到最后一个任务完成,系统会调用该方法。
        在这个方法中通常可以进行UI更新,并调用completionHandler通知系统已经完成所有操作。
    */
    - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
    {
        AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    
        //这中间就可以写更新UI的代码了,code
    
        if (appDelegate.backgroundSessionCompletionHandler) {
            void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;
            appDelegate.backgroundSessionCompletionHandler = nil;
            completionHandler();  
        }
    }
    
    如果喜欢我的文章,请关注我O(∩_∩)O哈!,求土豪打赏!有问题也可以提出来!

    相关文章

      网友评论

      • MR_詹:讲的超详细的,受益匪浅
      • Double丶K:问下,put和delete请求要怎么实现 :smile:
      • 小凡凡520:为什么我的下载一进入后没多久后就出现 下载超时呢
        执着丶执念:@小凡凡520 先看你的网络URL地址是否能正常访问下载,再看是否有设置请求超时时间,再检查下手机的联网情况
      • 随意_M: :sob: 之前没有接触过这块,硬着头皮把网络两篇看了,似懂非懂
      • 颜sir:有没有弄过webservice的soap请求,最近在弄那个,有点烦
      • LoveY34:NSURLSession中cookie能讲解下吗?
        执着丶执念: @Lylhf 这个cookie我尽量找时间补充

      本文标题:iOS学习笔记13-网络(二)NSURLSession

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