IOS开发路上的故事iOS 开发每天分享优质文章iOS底层基础知识

iOS NSURLSession使用详解

2018-04-25  本文已影响1904人  慌莫染

一、整体介绍

二、使用的一般步骤

其核心就是对网络任务进行封装,实现多线程。比如将一个网络请求交给NSURLSession,最后NSURLSession将访问结果通过block回调返回,期间自动实现多线程,而且可以通过代理实现监听(是否成功,当前的进度等等); 大致分为3个步骤:

1 NSURL:请求地址,定义一个网络资源路径:

NSURL *url = [NSURL URLWithString:@"协议://主机地址/路径?参数&参数"];

解释如下:

2 NSURLRequest:请求,根据前面的NSURL建立一个请求:

NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];

参数解释如下:

    *   NSURLRequestUseProtocolCachePolicy = 0 //默认的缓存策略,使用协议的缓存策略
    *   NSURLRequestReloadIgnoringLocalCacheData = 1 //每次都从网络加载
    *   NSURLRequestReturnCacheDataElseLoad = 2 //返回缓存否则加载,**很少使用**
    *   NSURLRequestReturnCacheDataDontLoad = 3 //只返回缓存,没有也不加载,**很少使用**

另外,还可以设置其它一些信息,比如请求头,请求体等等,如下:

注意,下面的request应为NSMutableURLRequest,即可变类型

// 告诉服务器数据为json类型
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; // 设置请求体(json类型)
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil];
request.HTTPBody = jsonData; 

3 NSURLSession:创建NSURLSession发送请求

image image

三 举例

1.NSURLSession请求网络数据

下面以苹果提供的全局NSURLSession单例为例,代码如下:

/// 向网络请求数据
- (void)NSURLSessionTest {
    // 1.创建url
    // 请求一个网页
    NSString *urlString = @"http://www.cnblogs.com/mddblog/p/5215453.html";

// 一些特殊字符编码
    urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    NSURL *url = [NSURL URLWithString:urlString];
    
    // 2.创建请求 并:设置缓存策略为每次都从网络加载 超时时间30秒
    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.NSURLSession文件下载

/// 文件下载
- (void)NSURLSessionDownloadTaskTest { // 1.创建url
  NSString *urlString = [NSString stringWithFormat:@"http://localhost/周杰伦 - 枫.mp3"]; // 一些特殊字符编码
  urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  NSURL *url = [NSURL URLWithString:urlString]; // 2.创建请求
  NSURLRequest *request = [NSURLRequest requestWithURL:url]; // 3.创建会话,采用苹果提供全局的共享session
  NSURLSession *sharedSession = [NSURLSession sharedSession]; // 4.创建任务
  NSURLSessionDownloadTask *downloadTask = [sharedSession downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil) { // location:下载任务完成之后,文件存储的位置,这个路径默认是在tmp文件夹下! // 只会临时保存,因此需要将其另存
          NSLog(@"location:%@",location.path); // 采用模拟器测试,为了方便将其下载到Mac桌面 // NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
          NSString *filePath = @"/Users/userName/Desktop/周杰伦 - 枫.mp3";
          NSError *fileError;
          [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:filePath error:&fileError]; if (fileError == nil) {
              NSLog(@"file save success");
          } else {
              NSLog(@"file save error: %@",fileError);
          }
      } else {
          NSLog(@"download error:%@",error);
      }
  }]; // 5.开启任务
[downloadTask resume];
}

3.NSURLSession文件上传

3.1 采用uploadTask任务,以数据流的方式进行上传
这种方式好处就是大小不受限制,上传需要服务器端脚本支持,脚本源代码见本文档最后的附录,客户端示例代码如下:

/// 以流的方式上传,大小理论上不受限制,但应注意时间
- (void) NSURLSessionBinaryUploadTaskTest { // 1.创建url  采用Apache本地服务器
    NSString *urlString = @"http://localhost/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:@"/Users/userName/Desktop/IMG_0359.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];
}

3.2 采用dataTask任务,拼接表单的方式进行上传

注意:然而在苹果官方对uploadTaskWithRequest函数的介绍:request的body data in this request object are ignored,会被忽略,而测试时发现没有被忽略,且request必须包含HTTPBody,反而fromData被忽略。那么暂时理解为苹果对uploadTaskWithRequest函数的使用时没有考虑拼接表单的方式,那么当我们使用拼接表单时,建议不要使用uploadTask,虽然这样也能成功

表单拼接格式如下,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"]; // 3.拼接表单,大小受MAX_FILE_SIZE限制(2MB)  FilePath:要上传的本地文件路径  formName:表单控件名称,应于服务器一致
    NSData* data = [self getHttpBodyWithFilePath:@"/Users/userName/Desktop/IMG_0359.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;
}

四 NSURLSessionConfiguration


NSURLConnection是全局性的,即它的配置对全局有效,如果有两个链接需要不同的cookies、证书这些公共资源,则NSURLConnection无法满足要求,这时NSURLSession的优势则体现出来,NSURLSession可以同过NSURLSessionConfiguration可以设置全局的网络访问属性。

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // delegateQueue:请求完成回调函数和代理函数的运行线程,如果为nil则系统自动创建一个串行队列,不影响sessionTask的运行线程
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];

三种会话方式:

  1. defaultSessionConfiguration:进程内会话(默认会话),类似 NSURLConnection的标准配置,用硬盘来缓存数据。
  2. ephemeralSessionConfiguration:临时的进程内会话(内存),不会将cookie、缓存储存到本地,只会放到内存中,当应用程序退出后数据也会消失,可以用于实现“秘密浏览”
  3. backgroundSessionConfiguration:建立后台会话可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务,后台另起一个线程。另外,系统会根据设备的负载程度决定分配下载的资源,因此有可能会很慢甚至超时失败。

设置一些网络属性:

configuration.HTTPAdditionalHeaders = @{ @"Accept": 
@"application/json", 
@"Accept-Language": 
@"en", 
@"Authorization": authString, 
@"User-Agent": userAgentString};

注意事项:如果是自定义会话并指定了代理,会话会对代理进行强引用,在视图控制器销毁之前,需要取消网络会话,否则会造成内存泄漏

上一篇 下一篇

猜你喜欢

热点阅读