3.NSURLSession &AFN &网络监

作者: SoftKnife | 来源:发表于2015-10-05 13:21 被阅读1196次
    People Lack Willpower,Rather Than Strength!
    

    1.NSURLSession

    • 本节主要涉及
      • NSURLSession的两个get请求/一个post请求
      • NSURLSessionTask抽象类,及其三个具象类:
        • 1.NSURLSessionDataTask
        • 2.NSURLSessionDownloadTask
        • 3.NSURLSessionUploadTask

    1.1 NSURLSession 基本使用

    • 使用步骤

      • 创建NSURLSession
      • 创建Task
      • 执行Task
    • 默认情况下创建的请求都是GET类型

      • GET1 : 使用request创建task
      // dataTask
      NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
      NSURLRequest *request = [NSURLRequest requestWithURL:url];
      
      // 1.创建session
      NSURLSession *session = [NSURLSession sharedSession];
      // 2.使用session创建task
      NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
      /*
        参数说明:
        data:服务器返回给我们的数据;
        response:响应头
        error:错误信息
        */
       // 通过该方法,我们可以对data进行简单处理
      NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
       
      // 2015-09-09 22:58:58.010 01-NSURLSession基本使用[3368:81761] {"success":"登录成功"}
      }];
      // 3.执行task
      [task resume];
      
      • GET2: 使用URL创建Task
      NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
      
      // 1.session创建
      NSURLSession *session = [NSURLSession sharedSession];
      // 2.使用session创建task
      // 如果通过传入url创建task,方法内部会自动创建一个request,略微省事;
      // 如果是发送get请求,或者不需要设置请求头信息,那么建议使用当前方法发送请求❤️
      NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
          NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
          
          /*
           2015-09-09 22:58:29.822 01-NSURLSession基本使用[3348:81294] {"success":"登录成功"}
           */
      }];
      
      // 3.执行task
      [task resume];
      
    • POST方法

      • 关键在于修改请求体中的HTTPMethod
      NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
      NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
      request.HTTPMethod = @"POST";
      request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
      
      // 1.创建session
      NSURLSession *session = [NSURLSession sharedSession];
      // 2.根据session,创建task
      NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
      NSLog(@"%@",task);
      //2015-09-09 22:57:28.105 01-NSURLSession基本使用[3322:80609] <__NSCFLocalDataTask: 0x7f86c170c850>{ taskIdentifier: 1 } { suspended }
      
      // 3.执行task
      [task resume];
      

    1.2 NSURLSessionDownloadTask

    NSURLSessionDownloadTask简单使用

    • 注意:

      • 默认情况下,使用NSURLSessionDownloadTask时,系统已经帮我们实现边下载边存储.防止内存暴增.
      • 而需要我们做的只是将下载的资源从不安全的tmp文件夹挪到caches文件夹.
      NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
      NSURLRequest *request = [NSURLRequest requestWithURL:url];
      
      // NSURLSessionDownloadTask
      // 1.创建session
      NSURLSession *session = [NSURLSession sharedSession];
      
      // 2.根据session创建task
      NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
       //查看是否下载成功.这里reponse正是类型是NSHTTPURLResponse❤️
       NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response;    NSLog(@"%ld,%zd",resp.statusCode,resp.expectedContentLength);
       // 参数解释:
       // location 即是下载好的资源在沙盒中的地址
       // NSURLSessionDownloadTask已经默认帮我们实现了,边下载边写入沙盒,以防内存暴增;
       
       // 查看默认下载地址
       NSLog(@"%@",location);
       // 默认是在tmp文件夹下,不安全,我们需要做的只是把它移到我们需要的位置:caches
       
       // 使用NSFileManager
       NSFileManager *manager = [NSFileManager defaultManager];
       // 将下载好的文件转移至caches
       //  NSString *toPath = [[location lastPathComponent] cacheDir];
       NSString *toPath = [response.suggestedFilename  cacheDir];
       [manager moveItemAtURL:location toURL:[NSURL fileURLWithPath:toPath] error:nil];
      }
      // 3.执行task
      [task resume];
      
      //==========================================
      // 通过url创建task
      [session downloadTaskWithURL:<#(NSURL *)#> completionHandler:<#^(NSURL *location, NSURLResponse *response, NSError *error)completionHandler#>]
      // 通过上次下载中断中处创建新task-->断点下载
      [session downloadTaskWithResumeData:<#(NSData *)#> completionHandler:<#^(NSURL *location, NSURLResponse *response, NSError *error)completionHandler#>];
      

    NSURLSessionDownloadTask监听下载进度

    • downloadTask可以暂停/取消/恢复下载
      • 这里,无论从suspended还是canle中resume,都是程序正常状态下的;
      • 如果程序意外中断,downloadTask提供的方法还不足以完成,dataTask可以完成!❤️
      • 注意: 使用代理方法时,不要使用block方法处理返回数据.否则代理方法失效.
    • 监听下载进度
    - (void)viewDidLoad
    {
      [super viewDidLoad];
      // 保存下载路径
      self.path = [@"minion_02.mp4" cacheDir]; 
    }
    /**开始下载 */
    - (IBAction)download:(id)sender {
    
      NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
      
      // 1.创建session
      /*
       第一个参数:Session的配置信息
       第二个参数: 代理
       第三个参数: 决定了代理方法在哪个线程中执行
       */
       self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
      
      // 2.根据session创建downloadTask
       self.task = [self.session downloadTaskWithURL:url];
       // 注意:这里如果使用回调方法,那么代理方法就不起作用了!!!❤️🈲
      //    [session downloadTaskWithURL:<#(NSURL *)#> completionHandler:<#^(NSURL *location, NSURLResponse *response, NSError *error)completionHandler#>]
      
      // 3.执行Task
      [self.task resume];
    }
    
    // =========================================== 
    // 接收到服务器反馈的数据是调用,开始写入数据
    /*注意:该方法会调用一次或者多次
    bytesWritten:当前写入数据量;
    totalBytesWritten:总共写入数据量;
    totalBytesExpectedToWrite:服务器返回给我们的文件大小
    */
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
    {
      
      self.progressView.progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
      
    }
    // 写完数据调用
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
    {
      NSLog(@"didFinishDownloadingToURL");
    
      // 数据写入完成时,要把数据从tmp文件夹转移至caches
      NSFileManager *manager = [NSFileManager defaultManager];
      NSURL *toUrl = [NSURL fileURLWithPath:self.path];
      [manager moveItemAtURL:location toURL:toUrl error:nil];
    
    }
    // 下载完成
    // 如果调用该方法时,error有值,表示下载出现错误
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
      NSLog(@"didCompleteWithError");
      
    }
    
    // 恢复下载时调用
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
    {
      NSLog(@"didResumeAtOffset");
    }
    
    // ===================取消/恢复=================== 
    /**暂停*/
    - (IBAction)pause:(id)sender {
      [self.task suspend];
    }
    
    /**  从暂停中恢复*/
    - (IBAction)pause2goon:(id)sender {
      
      [self.task resume];
    }
    
    /**取消 */
    - (IBAction)cance:(id)sender {
        // 注意这里如果使用这样的取消,那么就没办法恢复了!❤️
      //  [self.task cancel];
    
      // 如果是调用cancelByProducingResumeData方法, 方法内部会回调一个block, 在block中会将resumeData传递给我们
      // resumeData中就保存了当前下载任务的配置信息(下载到什么地方, 从什么地方恢复等等)❤️
      [self.task cancelByProducingResumeData:^(NSData *resumeData) {
          self.resumeData = resumeData;
      }];
    }
    
    /** 从取消中恢复*/
    - (IBAction)cance2goon:(id)sender {
      
      // 从上次中断数据处新建下载任务
      self.task = [self.session downloadTaskWithResumeData:self.resumeData];
      [self.task resume];
    }
    

    1.3 NSURLSessionDataTask

    NSURLSessionDataTask代理方法

    • 注意:NSURLSessionDataTask的代理方法中,默认情况下是不接受服务器返回的数据的.如果想接受服务器返回的数据,必须手动告诉系统,我们需要接收数据⚠️
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
       NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
       NSURLRequest *request = [NSURLRequest requestWithURL:url];
       
       // 1.创建session
       NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
       // 2.根据session创建Task
       NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
       
       // 3.执行Task
       [task resume];
    }
    
    // ===================代理方法====================
    #pragma mark - NSURLSessionDataDelegate
    // 服务器响应时调用
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
    {
       NSLog(@"didReceiveResponse");
       // 参数解释:⚠️
       // 系统默认是不会去调用didReceiveData和didCompleteWithError,必须手动告诉系统,我们需要接收数据
       /* 可见:NSURLSessionResponseDisposition默认== 0 ,也就是说默认是不接收数据的❤️
        typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
        NSURLSessionResponseCancel = 0,      Cancel the load == -[task cancel]
       NSURLSessionResponseAllow = 1,        Allow the load to continue
       NSURLSessionResponseBecomeDownload = 2,    Turn this request into a download
        */
        
       completionHandler(NSURLSessionResponseAllow);
    }
    
    // 收到服务器返回的数据时调用
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
       NSLog(@"didReceiveData");
    }
    
    // 请求完毕时调用,如果error有值,代表请求失败
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
       NSLog(@"didCompleteWithError");
    }
    

    NSURLSessionDataTask断点下载

    • 说明:
      • 由于dataTask并不擅长下载任务,所以如果用其完成断点下载,那么还是得自己使用文件句柄,或者输出流完成! 同时在request中设置下载位置.
      • 显然这并不是好的,但是使用擅长下载的downloadTask,貌似我们目前又很难从实现意外中断恢复继续下载;
    • 注意: 这里是使用DataTask完成下载, 并不是专业的,无法从cancel重恢复.
    - (void)viewDidLoad {
       [super viewDidLoad];
       // 初始化操作
       // 1.初始化路径
       self.path = [@"" cacheDir];
       // 2.初始化currentLength
       self.currentLength = [self fileDataSize:self.path];
    }
    
    // ==============================================
    #pragma mark - lazy
    - (NSURLSession *)session
    {
      if (!_session) {
          // 1.创建session
          _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
      }
      
      return _session;
    }
    
    - (NSURLSessionDataTask *)task
    {
      if (!_task) {
          //如果想实现意外中断后的继续下载,NSURLSessionDataTask也需要通过设置请求头的Range来实现❤️❤️
          NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
          NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
          
          // 设置请求头❤️⭐️◀️
          NSString *range = [NSString stringWithFormat:@"bytes:%zd-",[self fileDataSize:self.path]];
          [request setValue:range forHTTPHeaderField:@"Range"];
          
          // 2.根据session创建Task
          _task = [self.session dataTaskWithRequest:request];
        
      }
      return _task;
    }
    // 文件大小
    - (NSUInteger)fileDataSize:(NSString *)path
    {
      NSFileManager *manager = [NSFileManager defaultManager];
      
      NSDictionary *dict = [manager attributesOfItemAtPath:path error:nil];
      
      return [dict[NSFileSize] integerValue];
    }
    // 输出流获得
    - (NSOutputStream *)outputStream
    {
      if (!_outputStream) {
          _outputStream = [NSOutputStream outputStreamToFileAtPath:self.path append:YES];
          //输出流开启
          [_outputStream open];         
          //NSLog(@"输出流");
      }
      return _outputStream;
    }
    
    /** 开始下载
    */
    - (IBAction)download:(id)sender {
      [self.task resume];
    }
    
    // ======================代理方法====================
    
    #pragma mark - NSURLSessionDataDelegate
    // 服务器响应时调用
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
    {
      completionHandler(NSURLSessionResponseAllow);
    
      // 数据总大小
      self.totalLength = response.expectedContentLength + [self fileDataSize:self.path];
      // 感觉 self.currentLength = [self fileDataSize:self.path]😖可以试试
    }
    // 收到服务器返回的数据时调用,调用一次或者多次
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
      // 输出流写入数据
      [self.outputStream write:data.bytes maxLength:data.length];
      
      // 计算进度
      self.currentLength += data.length;
      self.progressView.progress = 1.0 * self.currentLength / self.totalLength;
    }
    
    // 请求完毕时调用,如果error有值,代表请求失败,由于没有didFinish方法,所以一旦完成下载,就会掉该方法,和downloadTask不同!❤️
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
      // 关闭输出流
      [self.outputStream close];
    }
    

    1.4.NSURLSessionUploadTask

    1.4.1.文件上传

    • 说明:

      • 和NSURLConnection中的文件上传区别在于:
        • 设置请求体时,不可以request.HTTPBody = bodyData ; 需要将bodyData设置到创建的task中.
      // 设置请求头
      request.HTTPMethod = @"POST";
      [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", XMGBoundary] forHTTPHeaderField:@"Content-Type"];
      
      // 设置请求体
      NSMutableData *body = [NSMutableData data];
      // 文件参数
      // 分割线
      [body appendData:XMGEncode(@"--")];
      [body appendData:XMGEncode(XMGBoundary)];
      [body appendData:XMGNewLine];
      .....
      
      // 注意这里通过设置请求体 = data完成文件上传,官方说这样做会被忽略
      // 就是说, 如果利用NSURLSessionUploadTask上传文件, 那么请求体必须写在fromData参数中, 不能设置在request中. 否则设置在request中会被忽略
      /*
      The body stream and body data in this request object are ignored.
      */
      // request.HTTPBody = data; ❌
      
      // 1.创建session
      NSURLSession *session = [NSURLSession sharedSession];
      
      // 2.根据session创建Task
      //注意这里的data是文件参数和非文件参数的拼接二进制❤️
      NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData: body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
         NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
      }];
      
      // 3.执行Task
      [task resume];
      
      // 注意:不要使用这个方法. fromFile方法是用于PUT请求上传文件的
        // 而我们的服务器只支持POST请求上传文件 
      [session uploadTaskWithRequest:request fromFile:fileUrl completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {}];❌
      

    1.4.2.文件上传的监听

        - 关键: 设置代理及实现对应代理方法即可
        - 核心代码:
    
    ........
    // 1.创建session
     NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
     
     // 2.根据session创建Task
     // 该方法中有回调函数,会影响代理方法调用🈲
     // NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { }];
     
     NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:body];
     
     // 3.执行Task
     [task resume];
    
    // =================代理方法====================
    #pragma mark - NSURLSessionTaskDelegate
    // 上传过程中调用
    /*
    bytesSent:当前这一次上传的数据大小;
    totalBytesSent:总共上传数据大小
    totalBytesExpectedToSend:需要上传的文件大小
    */
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
    {
      NSLog(@"didSendBodyData");
      NSLog(@"%f",1.0 * totalBytesSent/totalBytesExpectedToSend);
    }
    
    // 请求完毕时调用
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    { 
       NSLog(@"didCompleteWithError"); 
    }
    

    2.AFN

    2.1 NSURLConnection的封装

    • 关键: 拿到AFHTTPRequestOperationManager 对象
    • get 方法
    // 1.创建manager
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    
    // 2.使用manager发送get任务
    /*
    NSString:需要请求的url地址字符串
    parameters: 请求是需要传递的参数,需要以字典形式
    success:请求成功时回调的函数
    failure:请求失败时的回调函数
    */
    // 注意AFNetworking的get方法发送请求时,参数和资源地址是分开写的!❤️
    NSString *path = @"http://120.25.226.186:32812/login";
    NSDictionary *para = @{
                          @"username":@"520it",
                          @"pwd":@"520it",
                          @"type":@"XML"
                          };
    [manager GET:path parameters:para success:^(AFHTTPRequestOperation *operation, id responseObject) {  
       /*
        responseObject:
        这里默认服务器返回给我们的数据是JSON数据,然后会自动把数据转换为OC对象;
        如果真实返回类型不是JSON,那么默认情况下不会回调success block,直接回调failure block
        */
       NSLog(@"%@",responseObject); 
       /*服务器返回数据是JSON数据时,打印如下:
        2015-09-09 14:58:41.587 08-ANF基本使用[3605:115247] {
        success = "\U767b\U5f55\U6210\U529f";
        }
        */
    } failure:^(AFHTTPRequestOperation *operation, NSError *error){
       NSLog(@"error");
       /*当服务器返回数据类型不是JSON时,直接回调failure函数
        2015-09-09 15:00:14.309 08-ANF基本使用[3685:116977] error
        */
    }];
    
    • post 方法
    // 1.创建requestOperationManager
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    
    // 2.使用manager发送post请求
    NSString *path = @"http://120.25.226.186:32812/login";
    NSDictionary *para = @{
                           @"username":@"520it",
                           @"pwd":@"520it",
                           @"type":@"XML"
                           };
    
    [manager POST:path parameters:para success:^(AFHTTPRequestOperation *operation, id responseObject) {
        
        
        NSLog(@"%@",responseObject);
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"error");
    }];
    

    2.2 NSURLSession的封装

    • 关键: 拿到AFHTTPSessionManager 对象
    • get 方法
    // 1.创建manager
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    
    // 2.利用manager发送get请求
    NSString *path = @"http://120.25.226.186:32812/login";
    NSDictionary *para = @{
                          @"username":@"520it",
                          @"pwd":@"520it",
                          @"type":@"XML"
                          };
    
    [manager GET:path parameters:para success:^(NSURLSessionDataTask *task, id responseObject) {
       
       NSLog(@"%@",responseObject);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
       NSLog(@"error");
    }];
    
    • post 方法
    // 1.创建manager
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 2.利用manager发送post请求
    NSString *path = @"http://120.25.226.186:32812/login";
    NSDictionary *para = @{
                       @"username":@"520it",
                       @"pwd":@"520it",
                       @"type":@"XML"
                       };
    
    [manager POST:path parameters:para success:^(NSURLSessionDataTask *task, id responseObject) {
    
       NSLog(@"%@",responseObject);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
       NSLog(@"error");
    }];
    

    2.3 AFN下载

    • download

      • 注意 : 该方法需要resume
      // 1.创建manager
      AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
      
      // 2.使用manager创建下载任务
      NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"]];
      
      NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
          // 请求完成的回调
          // targetPath:文件默认下载后保存的路径
          // response:响应头
          // NSURL:block返回值,告诉AFN框架,是否需要将下载的文件转移到其他地方
          NSString *path = [response.suggestedFilename cacheDir];
          return [NSURL fileURLWithPath:path];
          
      } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
          // 下载完成的回调
          // filePath:移动之后的文件路径
          NSLog(@"filePath=%@",filePath);
          /*
           2015-09-09 15:44:08.476 08-ANF基本使用[4101:136108] filePath=file:///Users/PlwNs/Library/Developer/CoreSimulator/Devices/80A80097-63B4-4AB9-8B6B-1A30CCF465BE/data/Containers/Data/Application/0D45FB84-ED1B-4D5B-8401-B9FF2A9AB386/Library/Caches/minion_02.png
           */
      }];
      
      // 3.恢复下载
      [task resume];
      
    • 监听下载进度

      • 关键: 需要使用KVO 进行属性监听.

        • 主代码

      -(void)monitorDownloadProgress
      {
      // 1.创建sessionManager
      AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
      // 2.使用sessionManager创建task
      NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"]];

      NSProgress *progress = nil;
      self.progress = progress;
      //说明,这里任务被加入线程循环中,然后根据progress的地址,不断根据下载进度不断更新progress,所以我们才可以监听进度❤️
      NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
      NSString *path = [response.suggestedFilename cacheDir];
      return [NSURL fileURLWithPath:path];
      } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError error) {
      NSLog(@"filePath=%@",filePath);
      }];
      // 上述方法只会被调用一次,无法监控progress,只能使用属性和代理方法⚠️;
      // 不过可惜,这里无法设置代理,只能通过KVO,通过KVO,那么这里我们就无需定义属性了!!!❤️
      // 那么如何通过监控progress,拿到下载进度那?经查勘了解到,progress有两个属性
      /
      NSProgress只是一个对象!如何跟踪进度!-> KVO 对属性变化的监听!
      @property int64_t totalUnitCount: 需要下载的文件的总大小
      @property int64_t completedUnitCount:已经下载的文件大小
      */
      // 让self监控progress的属性变化
      [progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil];

      // 3.task resume
      [task resume];
      /*结果如下
      2015-09-09 16:12:26.451 08-ANF基本使用[4430:150386] 0.00027
      2015-09-09 16:12:26.509 08-ANF基本使用[4430:150388] 0.00043
      2015-09-09 16:12:26.585 08-ANF基本使用[4430:150386] 0.00088
      ...................
      */
      }

       ```
      
        - KVO
        
        ```objc
        -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
        {
          // 根据开发经验,由于一个project中可能存在诸多被observed对象,所以在coding时,一定记得判断object类型❤️❤️❤️❤️❤️
         // 与跳转相似
         if ([object isKindOfClass:[NSProgress class]]) {
             NSProgress *p = (NSProgress *)object;
             // 下载进度
             NSLog(@"%f",1.0 * p.completedUnitCount / p.totalUnitCount);
          // 获得准确进度
          /**
           准确的获得进度
           localizedDescription               10%
           localizedAdditionalDescription     completed 32,768 of 318,829
           fractionCompleted         0.102776(completedUnitCount/totalUnitCount)
           */
            NSLog(@"%@, %@, %f", p.localizedDescription, p.localizedAdditionalDescription, p.fractionCompleted);
         }
        }   
        ```
      
      -  勿忘移除监听❤️
            
        ```objc
         -(void)dealloc
            {
               [self.progress removeObserver:self forKeyPath:@"completedUnitCount"];
            }
        ```
      

    2.4 upload

    • 注意: AFNetworking 框架上传数据时,不是使用uploadTask ,如果这个,还得拼接上传请求体格式💔⚠️
    • 我们使用AFN中的POST: constructingBodyWith: 方法
    - (void)upload
    {
    // 1.创建manager
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    
    // 2.利用manager post文件
    NSString *path = @"http://120.25.226.186:32812/upload";
    NSDictionary *para = @{
                               @"username":@"pj"
                              };
    /*
    参数说明:
    参数一:上传服务器地址;
    参数二:非文件参数;
    formData:用来存储需要用来上传的文件二进制数据
    */
    [manager POST:path parameters:para constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
       
       // 在这个block中上传文件数据
       // formData就是专门用于保存需要上传文件的二进制数据的
       // formData如何存储数据?三个方法:
       
       // 1.appendPartWithFileData:
       /*参数说明
        NSData: 需要上传的文件二进制数据
        name: 上传服务器字段(服务器对应的参数名称)
        fileName:服务器上保持该文件的名称
        mimeType:文件content-type(MIMETYPE)
        */
       NSData *data = [NSData dataWithContentsOfFile:@"/Users/PlwNs/Desktop/座次表.png"];
       [formData appendPartWithFileData:data name:@"file" fileName:@"table.png" mimeType:@"image/png"];
       /*
        2015-09-09 17:04:41.693 08-ANF基本使用[4692:168259] 成功回调{
        success = "\U4e0a\U4f20\U6210\U529f";
        }
        */
       //--------------------------------------------------
       // 2.appendPartWithFileURL:
        NSURL *url = [NSURL fileURLWithPath:@"/Users/PlwNs/Desktop/座次表.png"];
        [formData appendPartWithFileURL:url name:@"file" error:nil];
       /*
        2015-09-09 17:08:15.797 08-ANF基本使用[4770:170215] 成功回调{
        success = "\U4e0a\U4f20\U6210\U529f";
        }
        */
       
       //---------------------------------------------------
       // 3.appendPartWithFileURL:
       NSURL *url = [NSURL fileURLWithPath:@"/Users/PlwNs/Desktop/座次表.png"];
       [formData appendPartWithFileURL: url name:@"file" fileName:@"abc.png" mimeType:@"image/png" error:nil];
       /*
        2015-09-09 17:09:50.544 08-ANF基本使用[4806:171112] 成功回调{
        success = "\U4e0a\U4f20\U6210\U529f";
        }
        */
       //---------------------------------------------------
       // 4.注意该方法不是用来上传数据的🈲
       // [formData appendPartWithFormData:<#(NSData *)#> name:<#(NSString *)#>]
       // } success:^(NSURLSessionDataTask *task, id responseObject) {
       //} failure:^(NSURLSessionDataTask *task, NSError *error) {
       //}];
    
      // 这个方法也不行,还得拼接二进制data,太麻烦了!!❤️
    //  NSURLSessionUploadTask *task = [manager uploadTaskWithRequest:request fromData:<#(NSData *)#> progress:<#(NSProgress *__autoreleasing *)#> completionHandler:<#^(NSURLResponse *response, id responseObject, NSError *error)completionHandler#>];
      // 这个是PUT请求,post请求不能使用🈲
      // [manager uploadTaskWithRequest:<#(NSURLRequest *)#> fromFile:<#(NSURL *)#> progress:<#(NSProgress *__autoreleasing *)#> completionHandler:<#^(NSURLResponse *response, id responseObject, NSError *error)completionHandler#>];
    }
    
    

    2.5 序列化

    • JSON数据
    - (void)serializerJSON
    {
       // 1.创建manager
       AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
       
       // 如果现实不出来,加上下面两句话❤️ 一般情况不会解析不出来,因为AF框架默认就是解析JSON数据.
       manager.responseSerializer = [AFJSONResponseSerializer serializer];
       [AFJSONResponseSerializer serializer].acceptableContentTypes = [NSSet setWithObject:@"text/json"];
       
       
       // 2.根据manager执行post login请求
       NSString *path = @"http://120.25.226.186:32812/login";
       NSDictionary *paraDict = @{
                                  @"username":@"520it",
                                  @"pwd":@"520it",
                                  @"type":@"JSON"
                                  };
       [manager POST:path parameters:paraDict success:^(NSURLSessionDataTask *task, id responseObject) {
           NSLog(@"%@",responseObject);
           
          /*很显然,被AFN框架自动转换成字典对象了
           2015-09-09 18:02:24.647 08-ANF基本使用[5863:209466] {
           success = "\U767b\U5f55\U6210\U529f";
           }
           */
           // 如果上面字典中type 不是 JSON , 那么直接进入failure!除非提前说明.
       } failure:^(NSURLSessionDataTask *task, NSError *error) {
           NSLog(@"error");
       }];
    }
    
    • XML数据
      • 如果服务器返回的数据不是JSON,我们如何提前通知AFN框架?
      • 如果提前告知AFN框架服务器返回数据是XML类型,那么框架就会将返回一个解析器对象(也作出了处理)❤️
    - (void)serializerXML
    {
    // 1.创建manager
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    
    // 告诉AFN框架,服务器返回数据类型
    // 1.1 AFN将服务器返回数据看做是XML类型,不做处理
    manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
    // 如果还出错,加下面这句话❤️
    [AFXMLParserResponseSerializer serializer].acceptableContentTypes = [NSSet setWithObject:@"text/xml"];;
    
    // 2.根据manager执行post login请求
    NSString *path = @"http://120.25.226.186:32812/login";
    NSDictionary *paraDict = @{
                               @"username":@"520it",
                               @"pwd":@"520it",
                               @"type":@"XML"
                               };
    [manager POST:path parameters:paraDict success:^(NSURLSessionDataTask *task, id responseObject) {
        
        //只要设置AFN的responseSerializer为XML, 那么返回的responseObject就是NSXMLParser解析器,而不是数据对象了❤️
        NSLog(@"%@",responseObject);
        /*
         08-ANF基本使用[6150:229599] <NSXMLParser: 0x7fa95ccc9920>
         */
        // 这里可以再解析
        // NSXMLParser *parser = (NSXMLParser *)responseObject;
        // parser.delegate = self;
        // [parser parse];
        
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        NSLog(@"error");
    }];
    }
    
    • 二进制数据
      • 如果提前告知AFN框架服务器返回数据是二进制类型,也就是说不做任何处理❤️
    - (void)serializer
    {
       // 1.创建manager
       AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
       
       // 告诉AFN框架,服务器返回数据类型
       // 1.2 AFN将服务器返回数据看做是二进制类型,也就是说不做任何处理❤️
       manager.responseSerializer = [AFHTTPResponseSerializer serializer];
       
       // 2.根据manager执行post login请求
       // 测试下载
       NSString *path = @"http://120.25.226.186:32812/resources/images/minion_02.png"; 
       // 这种写法POST/GET都适用❤️
       // POST方法为什么不行!!!😖😖😖
       // -------->模拟器问题,reset下就好了他妈的干干干.....
       [manager POST:path parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
           
           NSLog(@"%@",responseObject);
           /* 结果:
            2015-09-09 19:44:02.707 08-ANF基本使用[6642:243829] <3c21444f 43545950 45206874 6d6c2050 55424c49 4320222d 2f2f5733 432f2f44 54442048 544d4c20 342e3031 20547261 6e736974 696f6e61 6c2f2f45 4e222022 68747470 3a2f2f77 77772e77 332e6f72 672f5452 ...........
            */
           
       } failure:^(NSURLSessionDataTask *task, NSError *error) {
           NSLog(@"error");
       }];
    }
    

    2.6 AFN 解耦

    • KEY :
      • 自定义单例继承Manager
      • 优点: 替换框架只需要求改单例类即可
        • NSURLConnection 封装
    // PJNetworkingTool1.h 文件==================
    #import "AFHTTPRequestOperationManager.h"
    @interface PJNetworkTool1 : AFHTTPRequestOperationManager
    - (instancetype)shareManager;
    @end
    
    // PJNetworkingTool1.m文件==================
    #import "PJNetworkTool1.h"
    @implementation PJNetworkTool1
    - (instancetype)shareManager
    {
        static id instance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 一般来说,为了重构,我们不这样写!❤️
    //        instance = [self shareManager];
            NSString *urlStr = @"http://120.25.226.186:32812/";
            
            instance = [[PJNetworkTool1 alloc] initWithBaseURL:[NSURL URLWithString:urlStr]];
        });
        
        return instance;
    }
    @end
    
    • NSURLSession 封装
    // .h文件
     #import "AFHTTPSessionManager.h"
    @interface PJNetworkTool2 : AFHTTPSessionManager
    - (instancetype)shareManager;
    @end    
    // .m 文件
    #import "PJNetworkTool2.h"
    @implementation PJNetworkTool2
    - (instancetype)shareManager
    {
        static id instance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            NSString *urlStr = @"http://120.25.226.186:32812/";
            instance = [[PJNetworkTool2 alloc] initWithBaseURL:[NSURL URLWithString:urlStr] sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        });
        
        return instance;
    }
    @end
    

    2.7 AFN问题

    • Xcode 7之后,使用手动导入AFNetworking,会有问题:
      • 不使用Cocoapods时,post下载总是不成功的!!

    3.网络监测

    3.1 苹果官方做法

    //苹果自家的网络监控
    #import "Reachability.h"
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
         // 1.创建reachability对象(蜂窝网/局域网都行)
        self.network = [Reachability reachabilityForLocalWiFi];
        
        // 2.让self通过通知中心监听reachability状态变化
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNetworkStatus) name:kReachabilityChangedNotification object:nil];
        
        // 3.reachability开始发通知
        [self.network startNotifier];
    }
    - (void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    - (void)getNetworkStatus
    {
        // 判断蜂窝网是否可得
        if ([Reachability reachabilityForInternetConnection].currentReachabilityStatus != NotReachable) {
            NSLog(@"当前为蜂窝网");
        }else if([Reachability reachabilityForLocalWiFi].currentReachabilityStatus != NotReachable){  // 判断局域网是否可得
            NSLog(@"当前为局域网");
        }else{
            NSLog(@"没有网络");
        }
    }
    

    3.2 AFNetworking

    - (void)AFMonitorNetwork
    {
        // 首先看看AFN框架如何做到监控网络状态
        // 1.创建网络监听管理者
        // 单例!
        AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
        //AFNetworkReachabilityManager *manager1 = [AFNetworkReachabilityManager sharedManager];
        //NSLog(@"%@\n%@",manager,manager1);
        /*
         <AFNetworkReachabilityManager: 0x7ff5618ab3a0>
         <AFNetworkReachabilityManager: 0x7ff5618ab3a0>
         */
        
        // 2.设置网络变化时的回调block
        /*
         AFNetworkReachabilityStatusUnknown          = 不能识别,
         AFNetworkReachabilityStatusNotReachable     = 没有网络,
         AFNetworkReachabilityStatusReachableViaWWAN = 蜂窝网,
         AFNetworkReachabilityStatusReachableViaWiFi = 局域网,
         */
        [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
            switch (status) {
                case AFNetworkReachabilityStatusReachableViaWWAN:
                    NSLog(@"蜂窝网");
                    break;
                    
                case AFNetworkReachabilityStatusReachableViaWiFi:
                    NSLog(@"局域网");
                    break;
                case AFNetworkReachabilityStatusNotReachable:
                    NSLog(@"没有网");
                    break;
                    
                default:
                    NSLog(@"不能识别");
                    break;
            }
        }];
        
        // 3.开始监听:这样就持续不断的监听了.....o(╯□╰)o
        [manager startMonitoring];
        /*2015-09-09 22:16:44.966 10-网络监测[2658:55183] 局域网*/
    }
    

    相关文章

      网友评论

      本文标题:3.NSURLSession &AFN &网络监

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