3.NSURLSession &AFN &网络监测

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] 局域网*/
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容