iOS - NSURLSession 网络请求

简介: 前言 NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0) @interface NSURLSession : NSObject @available(iOS 7.0, *) public class NSURLSession : NSObject1、NSURLSession在 iOS9.0 之后,以前使用的 NSURLConnection 过期,苹果推荐使用 NSURLSession 来替换 NSURLConnection 完成网路请求相关操作。

前言

    NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0) @interface NSURLSession : NSObject
    @available(iOS 7.0, *) public class NSURLSession : NSObject

1、NSURLSession

  • 在 iOS9.0 之后,以前使用的 NSURLConnection 过期,苹果推荐使用 NSURLSession 来替换 NSURLConnection 完成网路请求相关操作。

1.1 NSURLSession 功能

  • NSURLSession 具有断点续传,后台下载等相关功能。
  • 暂停、停止、重启网络任务,不再需要 NSOperation 封装。
  • 请求可以使用同样的配置容器中。
  • 不同的 session 可以使用不同的私有存储。
  • block 和委托可以同时起作用。
  • 可以直接从文件系统上传下载。

  • NSURLSession 的使用非常简单,先根据会话对象创建一个请求 Task,然后执行该 Task 即可。NSURLSessionTask 本身是一个抽象类,在使用的时候,通常是根据具体的需求使用它的几个子类。关系如下:

  • NSURLSessionDownloadTask <-- NSURLSessionTask --> NSURLSessionDataTask --> NSURLSessionUploadTask

1.2 发送 GET 请求

  • 使用 NSURLSession 发送 GET 请求的方法和 NSURLConnection 类似,整个过程如下:

    • 1)确定请求路径(一般由公司的后台开发人员以接口文档的方式提供),GET 请求参数直接跟在 URL 后面;
    • 2)创建请求对象(默认包含了请求头和请求方法【GET】),此步骤可以省略;
    • 3)创建会话对象(NSURLSession);
    • 4)根据会话对象创建请求任务(NSURLSessionDataTask);
    • 5)执行 Task;
    • 6)当得到服务器返回的响应后,解析数据(XML|JSON|HTTP)。

1.3 发送 POST 请求

  • 使用 NSURLSession 发送 POST 请求的方法和 NSURLConnection 类似,整个过程如下:

    • 1)确定请求路径(一般由公司的后台开发人员以接口文档的方式提供);
    • 2)创建可变的请求对象(因为需要修改),此步骤不可以省略;
    • 3)修改请求方法为 POST;
    • 4)设置请求体,把参数转换为二进制数据并设置请求体;
    • 5)创建会话对象(NSURLSession);
    • 6)根据会话对象创建请求任务(NSURLSessionDataTask);
    • 7)执行 Task;
    • 8)当得到服务器返回的响应后,解析数据(XML|JSON|HTTP)。

1.4 文件下载请求

  • 文件下载成功后,如果不做任何处理,下载的文件会被自动删除。
  • 如果显示比较大的图片,NSURLSession 可以利用磁盘缓存直接下载到本地,不会造成内存占用太大。

    • 一般从网络上下载文件,zip 压缩包会比较多。
    • 如果是 zip 文件,下载完成后需要。
      • 下载压缩包
      • 解压缩(异步执行)到目标文件夹
      • 删除压缩包
    • 下载任务的特点可以让程序员只关心解压缩的工作。

1.5 文件上传请求

  • POST:

    • 需要有一个脚本做支持。

    • 有些脚本有上传文件大小限制,如 PHP 最大为 2M。

  • PUT:

    • 不需要脚本,直接以文件的方式写入服务器。
    • 如果文件不存在,就是新增,如果文件存在就是修改。
    • 文件上传需要身份验证。

    • status code: 201 新增
    • status code: 204 修改

    • status code: 401 身份验证失败

2、NSURLSession 的设置

  • Objective-C

    • URLRequest 的设置

          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"]];
      
          // 设置缓存策略
          /*
              // 默认的缓存策略,会在本地缓存
              NSURLRequestUseProtocolCachePolicy = 0,
      
              // 忽略本地缓存数据,永远都是从服务器获取数据,不使用缓存,应用场景:股票,彩票
              NSURLRequestReloadIgnoringLocalCacheData = 1,
              NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData
      
              // 首先使用缓存,如果没有本地缓存,才从原地址下载
              NSURLRequestReturnCacheDataElseLoad = 2,                                       
      
              // 使用本地缓存,从不下载,如果本地没有缓存,则请求失败和 "离线" 数据访问有关,可以和 Reachability 框架结合使用,
              // 如果用户联网,直接使用默认策略。如果没有联网,可以使用返回缓存策略,郑重提示:要把用户拉到网络上来。
              NSURLRequestReturnCacheDataDontLoad = 3,
      
              // 无视任何缓存策略,无论是本地的还是远程的,总是从原地址重新下载
              NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,      // Unimplemented
      
              // 如果本地缓存是有效的则不下载,其他任何情况都从原地址重新下载
              NSURLRequestReloadRevalidatingCacheData = 5,                // Unimplemented
      
              缓存的数据保存在沙盒路径下 Caches 文件夹中的 SQLite 数据库中。
          */
          urlRequest.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
      
          // 设置超时时间
          urlRequest.timeoutInterval = 120;
      
          // 设置请求模式
          /*
              默认是 GET
          */
          urlRequest.HTTPMethod = @"POST";
      
          // 设置请求体
          urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
      
          // 设置请求头
          /*
              告诉服务器客户端类型,只能写英文,User-Agent 是固定的 key
          */
          [urlRequest setValue:@"iPhone 6s Plus" forHTTPHeaderField:@"User-Agent"];
    • URLSessionConfiguration 的设置

      • 在开发一款应用程序的时候,通常只会访问一台服务器,如果所有的设置在 session 中统一设置一次,后续的网络访问方法,会非常简单,一次设置,全局有效。
      • 在 URLSession 中,会使用 config 替代很多原有 request 中的附加设置。config 用于设置全局的网络会话属性,包括:浏览器类型,Content-Type,身份验证,Cookie,超时时长,缓存策略,主机最大连接数...。NSURLSessionConfiguration 拥有 20 个属性。熟练掌握这些属性的用处,将使应用程序充分利用其网络环境。

      • 常用属性:

            HTTPAdditionalHeaders           HTTP 请求头,告诉服务器有关客户端的附加信息,这对于跨会话共享信息,
                                             如内容类型,语言,用户代理,身份认证,是很有用的。
        
                Accept                      告诉服务器客户端可接收的数据类型,如:@"application/json" 。
                Accept-Language             告诉服务器客户端使用的语言类型,如:@"en" 。
                Authorization               验证身份信息,如:authString 。
                User-Agent                  告诉服务器客户端类型,如:@"iPhone AppleWebKit" 。
                range                       用于断点续传,如:bytes=10- 。
        
            networkServiceType              网络服务类型,对标准的网络流量,网络电话,语音,视频,以及由一个后台进程使用的流量
                                            进行了区分。大多数应用程序都不需要设置这个。
            NSURLNetworkServiceTypeDefault          默认
            NSURLNetworkServiceTypeVoIP             VoIP
            NSURLNetworkServiceTypeVideo            视频
            NSURLNetworkServiceTypeBackground       后台
            NSURLNetworkServiceTypeVoice            语音
        
            allowsCellularAccess            允许蜂窝访问,和 discretionary 自行决定,被用于节省通过蜂窝连接的带宽。
                                            建议在使用后台传输的时候,使用 discretionary 属性,而不是 allowsCellularAccess 
                                            属性,因为它会把 WiFi 和电源可用性考虑在内。
        
            timeoutIntervalForRequest       超时时长,许多开发人员试图使用 timeoutInterval 去限制发送请求的总时间,但这误会了
                                            timeoutIntervalForRequest 的意思:报文之间的时间。
            timeoutIntervalForResource      整个资源请求时长,实际上提供了整体超时的特性,这应该只用于后台传输,而不是用户实际上
                                            可能想要等待的任何东西。
        
            HTTPMaximumConnectionsPerHost   对于一个 host 的最大并发连接数,iOS 默认数值是 4,MAC 下的默认数值是 6,从某种程度上,
                                            替代了 NSOpeartionQueue 的最大并发线程数。是 Foundation 框架中 URL 加载系统的一个新
                                            的配置选项。它曾经被用于 NSURLConnection 管理私人连接池。现在有了 NSURLSession,开发
                                            者可以在需要时限制连接到特定主机的数量。日常开发中,几乎不用去管 session 的最大并发数。
        
            HTTPShouldUsePipelining         也出现在 NSMutableURLRequest,它可以被用于开启 HTTP 管道,这可以显着降低请求的加载时
                                            间,但是由于没有被服务器广泛支持,默认是禁用的。
        
            sessionSendsLaunchEvents        是另一个新的属性,该属性指定该会话是否应该从后台启动。
        
            connectionProxyDictionary       指定了会话连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不
                                            需要配置这个属性。关于连接代理的更多信息可以在 CFProxySupport Reference 找到。
        
            Cookie Policies
                HTTPCookieStorage           被会话使用的 cookie 存储。默认情况下,NSHTTPCookieShorage 的 sharedHTTPCookieStorage 
                                            会被使用,这与 NSURLConnection 是相同的。
                HTTPCookieAcceptPolicy      决定了该会话应该接受从服务器发出的 cookie 的条件。
                HTTPShouldSetCookies        指定了请求是否应该使用会话 HTTPCookieStorage 的 cookie。
        
            Security Policies
                URLCredentialStorage        会话使用的证书存储。默认情况下,NSURLCredentialStorage 的sharedCredentialStorage 会被
                                            使用,这与 NSURLConnection 是相同的。
        
            TLSMaximumSupportedProtocol     确定是否支持 SSLProtocol 版本的会话。
            TLSMinimumSupportedProtocol     确定是否支持 SSLProtocol 版本的会话。
        
            Caching Policies
                URLCache                    会话使用的缓存。默认情况下,NSURLCache 的sharedURLCache 会被使用,这与 NSURLConnection 
                                            是相同的。
                requestCachePolicy          缓存策略,指定一个请求的缓存响应应该在什么时候返回。这相当于 NSURLRequest 的 cachePolicy 
                                            方法。
        
            Custom Protocols
                protocolClasses             注册 NSURLProtocol 类的特定会话数组。
        
            NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        
            // 设置同时连接到一台服务器的最大连接数
        
                configuration.HTTPMaximumConnectionsPerHost = 4;
        
            // 设置授权信息,WebDav 的身份验证
        
                NSString *username = @"admin";
                NSString *password = @"adminpasswd";
        
                NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", username, password];
                NSData   *userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
                NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
                NSString *authString = [NSString stringWithFormat:@"Basic: %@", base64EncodedCredential];
        
            // 设置客户端类型
        
                NSString *userAgentString = @"iPhone AppleWebKit";
        
            // 设置请求头
        
                configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
                                                     @"Accept-Language": @"en",
                                                     @"Authorization": authString,
                                                     @"User-Agent": userAgentString};
    • URLSession 创建方式

          Important
      
              The session object keeps a strong reference to the delegate until your app explicitly invalidates the session. 
          If you do not invalidate the session by calling the invalidateAndCancel or resetWithCompletionHandler: method, 
          your app leaks memory.
      
              一旦指定了 session 的代理,session 会对代理进行强引用,如果不主动取消 session,会造成内存泄漏。
      
          释放强引用的办法:
      
              1> 网络操作完成:
      
                  取消 session 标记:
      
                  session 完成并且无效,已经被取消的会话,无法再次使用。
      
                  __weak typeof(self) weakSelf = self;
                  [weakSelf.session finishTasksAndInvalidate];
      
                  释放 session:
      
                  __weak typeof(self) weakSelf = self;
                  weakSelf.session = nil;
      
                  优点:能够保证下载任务的正常完成。
                  坏处:每一次网络访问结束后,都要销毁 session,会造成 session 的重复创建和销毁。
      
              2> 视图控制器销毁之前,将 session 释放掉:
      
                  viewWillDisappear 方法中,将 session 销毁
      
                  [self.session invalidateAndCancel];
                  self.session = nil;
      
                  好处:只会在视图控制器被销毁之前,才会释放 session,避免重复的创建和销毁。
                  缺点:session 被取消后,下载任务同样会被取消(有些版本的 Xcode)。
      
              3> 关于网络访问,通常都是建立一个网路访问的单例:
      
                  如果单例的工具类,本身就是 session 的代理,单例会随着引用程序被销毁,才会被释放。就不需要考虑 session 的释放问题。
      
          // 共享会话方式
      
              /*
                      为了方便程序员使用,苹果提供了一个全局 session,全局 session 的回调是异步的,所有的任务都是由 session 发起的。要跟进下
                  载进度,不能使用全局 session。
      
                  该会话使用全局的 Cache,Cookie 和证书。
              */
      
              NSURLSession *urlSession1 = [NSURLSession sharedSession];
      
          // 配置会话方式
      
              /*
                  + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
      
                  configuration:
      
                  + (NSURLSessionConfiguration *)defaultSessionConfiguration;
                  + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
      
                  NS_AVAILABLE(10_10, 8_0)
                  + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;
      
                      默认会话模式(default):工作模式类似于原来的 NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户钥匙串中保
                  存的证书进行认证授权。
      
                      瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的缓存,证书,cookies 等都被保存在 RAM 中,
                  因此当程序使会话无效,这些缓存的数据就会被自动清空。这对于实现像 "秘密浏览" 功能的功能来说,是很理想的。
      
                      后台会话模式(background):该模式在后台完成上传和下载,后台会话不同于常规的普通的会话,它甚至可以在应用程序挂起,退
                  出,崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。想要查
                  看更多关于后台会话的信息,可以查看WWDC Session 204: “What’s New with Multitasking”。
              */
      
              NSURLSession *urlSession2 = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
      
          // 配置会话协议方式
      
              /*
              + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration 
                                                delegate:(nullable id <NSURLSessionDelegate>)delegate 
                                           delegateQueue:(nullable NSOperationQueue *)queue;
      
                  queue:
      
                  注意:下载本身的线程 "只有一条",代理回调可以在 "多个线程" 回调,指定代理执行的队列,不会影响到下载本身的执行。
      
                  如何选择队列:网络访问结束后,如果不需要做复杂的操作,可以指定主队列,这样不用考虑线程间通讯
      
                  主队列回调:
      
                      [NSOperationQueue mainQueue]
      
                      代理方法在主线程中调用。
      
                      下载本身是异步执行的,这一点和 NSURLConnection 一样。
                      NSURLSession 即使在主线程回调也不会造成阻塞。
      
                  异步回调:
      
                      [[NSOperationQueue alloc] init]
                      nil
      
                      代理方法在子线程中调用。
      
                      二三两种方式可以创建一个新会话并定制其会话类型。该方式中指定了 session 的委托和委托所处的队列。当不再需要连接时,可以调用
                  Session 的 invalidateAndCancel 直接关闭,或者调用 finishTasksAndInvalidate 等待当前 Task 结束后关闭。这时 Delegate 
                  会收到 URLSession:didBecomeInvalidWithError: 这个事件。Delegate 收到这个事件之后会被解引用。
              */
      
              NSURLSession *urlSession3 = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                        delegate:self 
                                                                   delegateQueue:[NSOperationQueue mainQueue]];
    • Task 创建方式

          // 数据请求 NSURLSessionDataTask (GET/POST)
      
              // 数据请求 request block 方式
      
                  NSURLSessionDataTask *urlSessionDataTask1 = [urlSession1 dataTaskWithRequest:urlRequest 
                                                                             completionHandler:^(NSData * _Nullable data, 
                                                                                          NSURLResponse * _Nullable response, 
                                                                                                NSError * _Nullable error) {
                      // block 在子线程中执行
                  }];
      
              // 数据请求 request 协议 方式
      
                  // 遵守协议 <NSURLSessionDataDelegate>
                  NSURLSessionDataTask *urlSessionDataTask2 = [urlSession3 dataTaskWithRequest:urlRequest];
      
              // 数据请求 url block 方式
              /*
                  1)该方法内部会自动将请求路径包装成一个请求对象,该请求对象默认包含了请求头信息和请求方法(GET)
                  2)如果要发送的是 POST 请求,则不能使用该方法。
              */
      
                  NSURLSessionDataTask *urlSessionDataTask3 = [urlSession1 dataTaskWithURL:url 
                                                                         completionHandler:^(NSData * _Nullable data, 
                                                                                      NSURLResponse * _Nullable response, 
                                                                                            NSError * _Nullable error) {
                      // block 在子线程中执行
                  }];
      
              // 数据请求 url 协议 方式
              /*
                  1)该方法内部会自动将请求路径包装成一个请求对象,该请求对象默认包含了请求头信息和请求方法(GET)
                  2)如果要发送的是 POST 请求,则不能使用该方法。
              */
      
                  // 遵守协议 <NSURLSessionDataDelegate>
                  NSURLSessionDataTask *urlSessionDataTask4 = [urlSession3 dataTaskWithURL:url];
      
          // 文件下载 NSURLSessionDownloadTask
      
              // 文件下载 request block 方式
      
                  NSURLSessionDownloadTask *urlSessionDownloadTask1 = [urlSession1 downloadTaskWithRequest:urlRequest 
                                                                                         completionHandler:^(NSURL * _Nullable location,
                                                                                                     NSURLResponse * _Nullable response, 
                                                                                                           NSError * _Nullable error) {
      
                      // block 在子线程中执行
                      // location 是下载的文件临时存储路径,下载完成后会被自动删除
                  }];
      
              // 文件下载 request 协议 方式
      
                  // 遵守协议 <NSURLSessionDownloadDelegate>
                  NSURLSessionDownloadTask *urlSessionDownloadTask2 = [urlSession3 downloadTaskWithRequest:urlRequest];
      
              // 文件下载 url block 方式
      
                  NSURLSessionDownloadTask *urlSessionDownloadTask3 = [urlSession1 downloadTaskWithURL:url 
                                                                                     completionHandler:^(NSURL * _Nullable location, 
                                                                                                 NSURLResponse * _Nullable response, 
                                                                                                       NSError * _Nullable error) {
      
                      // block 在子线程中执行
                      // location 是下载的文件临时存储路径,下载完成后会被自动删除
                  }];
      
              // 文件下载 url 协议 方式
      
                  // 遵守协议 <NSURLSessionDownloadDelegate>
                  NSURLSessionDownloadTask *urlSessionDownloadTask4 = [urlSession3 downloadTaskWithURL:url];
      
              // 文件下载 resumeData block 方式
      
                  NSData *resumeData = nil;
      
                  NSURLSessionDownloadTask *urlSessionDownloadTask5 = [urlSession1 downloadTaskWithResumeData:resumeData 
                                                                                            completionHandler:^(NSURL * _Nullable location, 
                                                                                                        NSURLResponse * _Nullable response, 
                                                                                                              NSError * _Nullable error) {
                      // block 在子线程中执行
                      // 断点续传,resumeData 为之前已经下载的数据
                      // location 是下载的文件临时存储路径,下载完成后会被自动删除
                  }];
      
              // 文件下载 resumeData 协议 方式
      
                  // 遵守协议 <NSURLSessionDownloadDelegate>
                  NSURLSessionDownloadTask *urlSessionDownloadTask6 = [urlSession3 downloadTaskWithResumeData:resumeData];
      
          // 文件上传 NSURLSessionUploadTask
      
              // 文件上传 fromFile block 方式
      
                  NSURL *uploadFileUrl = nil;
      
                  NSURLSessionUploadTask *urlSessionUploadTask1 = [urlSession1 uploadTaskWithRequest:urlRequest 
                                                                                            fromFile:uploadFileUrl 
                                                                                   completionHandler:^(NSData * _Nullable data, 
                                                                                                NSURLResponse * _Nullable response, 
                                                                                                      NSError * _Nullable error) {
                      // block 在子线程中执行
                  }];
      
              // 文件上传 fromFile 协议 方式
      
                  // 遵守协议 <NSURLSessionDataDelegate>
                  NSURLSessionUploadTask *urlSessionUploadTask2 = [urlSession3 uploadTaskWithRequest:urlRequest fromFile:uploadFileUrl];
      
              // 文件上传 fromData block 方式
      
                  NSData *uploadFileData = nil;
      
                  NSURLSessionUploadTask *urlSessionUploadTask3 = [urlSession1 uploadTaskWithRequest:urlRequest 
                                                                                            fromData:uploadFileData 
                                                                                   completionHandler:^(NSData * _Nullable data, 
                                                                                                NSURLResponse * _Nullable response, 
                                                                                                      NSError * _Nullable error) {
                      // block 在子线程中执行
                  }];
      
              // 文件上传 fromData 协议 方式
      
                  // 遵守协议 <NSURLSessionDataDelegate>
                  NSURLSessionUploadTask *urlSessionUploadTask4 = [urlSession3 uploadTaskWithRequest:urlRequest fromData:uploadFileData];
      
              // 文件上传 Streamed Request 方式
      
                  NSURLSessionUploadTask *urlSessionUploadTask5 = [urlSession1 uploadTaskWithStreamedRequest:urlRequest];
    • Task 的设置

          // 开始 Task 任务
          [urlSessionDownloadTask1 resume];
      
          // 暂停 Task 任务
          [urlSessionDownloadTask1 suspend];
      
          // 取消 Task 任务
      
              // 完全取消,下次下载又从 0.0% 开始
              [urlSessionDownloadTask1 cancel];
      
              // 可恢复性取消,下次下载可从 保存的 resumeData 处开始
              [urlSessionDownloadTask1 cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
      
              }];
    • 文件下载设置

          // location 是下载的文件临时存储路径,下载完成后会被自动删除。response.suggestedFilename 为服务器端文件名。
      
          NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                                   stringByAppendingPathComponent:response.suggestedFilename];
      
          // 设置下载的文件存储路径
          /*
              处理下载的数据
          */
          [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
    • 文件上传设置

          #define boundary @"myBoundary"
      
          // 设置请求头
          /*
              upload task 不会在请求头里添加 content-type (上传数据类型)字段,@"myBoundary" 为请求体边界,参数可以随便设置,但需一致
          */
          [urlRequest setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] 
                                                                                      forHTTPHeaderField:@"Content-Type"];
      
          // 设置请求文件参数
      
              NSMutableData *fromBody = [NSMutableData data];
      
              // 参数开始分割线
              /*
                  每个参数开始前都需要加
              */
              [fromBody appendData:[[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
              // 参数
              [fromBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; 
                                                                                 name=\"%@\"\r\n\r\n%@", @"username", @"jhq"] 
                                                                                 dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
              // 文件开始分割线
              /*
                  每个文件开始前都需要加
              */
              [fromBody appendData:[[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
              // 文件参数名
              /*
                  test.png 为上传后服务器端文件名称
              */
              [fromBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; 
                                                                                name=\"%@\"; 
                                                                            filename=\"%@\"", @"file", @"test.png"] 
                                                                            dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
              // 文件的类型
              [fromBody appendData:[[NSString stringWithFormat:@"Content-Type: image/png"] dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
              // 待上传文件数据
              /*
                  本地待上传的文件路径
              */
              [fromBody appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"HQ_0005" ofType:@"jpg"]]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
              // 结束分割线标记
              [fromBody appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
              [fromBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

3、NSURLSession 异步 GET 数据请求

  • Objective-C

    • 使用 request block 回调方式

          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
      
          // 创建请求对象
          NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sharedSession];
      
          // 发送请求
          NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest 
                                                                   completionHandler:^(NSData * _Nullable data, 
                                                                                NSURLResponse * _Nullable response, 
                                                                                      NSError * _Nullable error) {
      
              // 处理从服务器下载的数据
              if (error == nil && data != nil) {
      
              }
          }];
      
          // 执行任务
          [urlSessionDataTask resume];
    • 使用 request 协议 方式

          // 遵守协议 <NSURLSessionDataDelegate>
      
          @property(nonatomic, retain)NSMutableData *asyncNetData;
      
          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
      
          // 创建请求对象
          NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                   delegate:self 
                                                              delegateQueue:[[NSOperationQueue alloc] init]];
      
          // 发送请求
          NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest];
      
          // 执行任务
          [urlSessionDataTask resume];
      
          // 协议方法
      
          // 接收到服务器的响应
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 
                                           didReceiveResponse:(NSURLResponse *)response 
                                            completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
              /*
                  需要使用 completionHandler 回调告诉系统应该如何处理服务器返回的数据,默认是取消的。
      
                  NSURLSessionResponseCancel = 0,             默认的处理方式,取消
                  NSURLSessionResponseAllow = 1,              接收服务器返回的数据
                  NSURLSessionResponseBecomeDownload = 2,     变成一个下载请求
                  NSURLSessionResponseBecomeStream            变成一个流
              */
      
              // 接收服务器返回的数据
              completionHandler(NSURLSessionResponseAllow);
      
              // 异步下载数据源初始化
              self.asyncNetData = [[NSMutableData alloc] init];
          }
      
          // 接收到服务器数据
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
      
              // 拼接从服务器下载的数据
              [self.asyncNetData appendData:data];
          }
      
          // 服务器的数据加载完毕
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {            
      
              if (error == nil) {
      
                  // 处理从服务器下载的数据
                  id result = [NSJSONSerialization JSONObjectWithData:self.asyncNetData options:0 error:NULL];
                  NSLog(@"异步 GET 网络请求完成: \n%@", result);
              }   
          }
    • 使用 url block 回调方式

          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sharedSession];
      
          // 发送请求
          NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithURL:url 
                                                               completionHandler:^(NSData * _Nullable data, 
                                                                            NSURLResponse * _Nullable response, 
                                                                                  NSError * _Nullable error) {
      
              // 处理从服务器下载的数据
              if (error == nil && data != nil) {
      
              }
          }];
      
          // 执行任务
          [urlSessionDataTask resume];
    • 使用 url 协议 方式

          // 遵守协议 <NSURLSessionDataDelegate>
      
          @property(nonatomic, retain)NSMutableData *asyncNetData;
      
          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=JSON"];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                   delegate:self 
                                                              delegateQueue:[[NSOperationQueue alloc] init]];
      
          // 发送请求
          NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithURL:url];
      
          // 执行任务
          [urlSessionDataTask resume];
      
          // 协议方法
      
          // 接收到服务器的响应
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 
                                           didReceiveResponse:(NSURLResponse *)response 
                                            completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
              /*
                  需要使用 completionHandler 回调告诉系统应该如何处理服务器返回的数据,默认是取消的。
      
                  NSURLSessionResponseCancel = 0,             默认的处理方式,取消
                  NSURLSessionResponseAllow = 1,              接收服务器返回的数据
                  NSURLSessionResponseBecomeDownload = 2,     变成一个下载请求
                  NSURLSessionResponseBecomeStream            变成一个流
              */
      
              // 接收服务器返回的数据
              completionHandler(NSURLSessionResponseAllow);
      
              // 异步下载数据源初始化
              self.asyncNetData = [[NSMutableData alloc] init];
          }
      
          // 接收到服务器数据
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
      
              // 拼接从服务器下载的数据
              [self.asyncNetData appendData:data];
          }
      
          // 服务器的数据加载完毕
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
              if (error == nil) {
      
                  // 处理从服务器下载的数据
                  id result = [NSJSONSerialization JSONObjectWithData:self.asyncNetData options:0 error:NULL];
                  NSLog(@"异步 GET 网络请求完成: \n%@", result);
              }
          }

4、NSURLSession 异步 POST 数据请求

  • Objective-C

    • 使用 request block 回调方式

          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];
      
          // 创建请求对象
          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
      
          // 设置请求方式,默认为 GET 请求
          urlRequest.HTTPMethod = @"POST";
      
          // 设置请求体(请求参数)
          urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sharedSession];
      
          // 发送请求
          NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest 
                                                                   completionHandler:^(NSData * _Nullable data, 
                                                                                NSURLResponse * _Nullable response, 
                                                                                      NSError * _Nullable error) {
      
          }];
      
          // 执行任务
          [urlSessionDataTask resume];
    • 使用 request 协议 方式

          // 遵守协议 <NSURLSessionDataDelegate>
      
          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];
      
          // 创建请求对象
          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
      
          // 设置请求方式,默认为 GET 请求
          urlRequest.HTTPMethod = @"POST";
      
          // 设置请求体(请求参数)
          urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                   delegate:self 
                                                              delegateQueue:[NSOperationQueue mainQueue]];
      
          // 发送请求
          NSURLSessionDataTask *urlSessionDataTask = [urlSession dataTaskWithRequest:urlRequest];
      
          // 执行任务
          [urlSessionDataTask resume];
      
          // 协议方法
      
          // 接收到服务器的响应
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 
                                           didReceiveResponse:(NSURLResponse *)response 
                                            completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
              /*
                  需要使用 completionHandler 回调告诉系统应该如何处理服务器返回的数据,默认是取消的。
      
                  NSURLSessionResponseCancel = 0,             默认的处理方式,取消
                  NSURLSessionResponseAllow = 1,              接收服务器返回的数据
                  NSURLSessionResponseBecomeDownload = 2,     变成一个下载请求
                  NSURLSessionResponseBecomeStream            变成一个流
              */
      
              // 接收服务器返回的数据
              completionHandler(NSURLSessionResponseAllow);
          }
      
          // 接收到服务器数据
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
      
          }
      
          // 服务器的数据加载完毕
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
          }

5、NSURLSession 文件下载

  • Objective-C

    • 使用 request block 回调方式

          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];
      
          // 创建请求对象                      
          NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sharedSession];
      
          NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithRequest:urlRequest 
                                                                               completionHandler:^(NSURL * _Nullable location, 
                                                                                           NSURLResponse * _Nullable response, 
                                                                                                 NSError * _Nullable error) {
      
              if (error == nil) {
      
                  // 设置下载的文件存储路径
                  NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                                        stringByAppendingPathComponent:response.suggestedFilename];
      
                  // 处理下载的数据
                  [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
              }
          }];
      
          // 执行任务
          [urlSessionDownloadTask resume];
    • 使用 request 协议 方式

          // 遵守协议 <NSURLSessionDownloadDelegate>
      
          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
      
          // 创建请求对象
          NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                   delegate:self 
                                                              delegateQueue:[[NSOperationQueue alloc] init]];
      
          // 发送请求
          NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithRequest:urlRequest];
      
          // 执行任务
          [urlSessionDownloadTask resume];
      
          // 协议方法
      
          // 监听下载进度,每当写入数据到临时文件时,就会调用一次这个方法
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                     didWriteData:(int64_t)bytesWritten 
                                                totalBytesWritten:(int64_t)totalBytesWritten 
                                        totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
      
              // 这次写入多少
              NSLog(@"bytesWritten: %lli", bytesWritten);
      
              // 已经写入的大小
              NSLog(@"totalBytesWritten: %lli", totalBytesWritten);
      
              // 总大小
              NSLog(@"totalBytesExpectedToWrite: %lli", totalBytesExpectedToWrite);
      
              float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
      
              dispatch_async(dispatch_get_main_queue(), ^{
      
                  // 设置下载进度条
                  self.progressView.progress = progress;
              });
          }
      
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                        didFinishDownloadingToURL:(NSURL *)location {
      
              // 设置下载的文件存储路径
              NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                       stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
      
              // 处理下载的数据
              [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
          }
      
          // 恢复下载任务时调用
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                didResumeAtOffset:(int64_t)fileOffset 
                                               expectedTotalBytes:(int64_t)expectedTotalBytes {
      
          }
      
          // 下载完成或中断
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
          }
    • 使用 url block 回调方式

          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sharedSession];
      
          NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithURL:url 
                                                                           completionHandler:^(NSURL * _Nullable location, 
                                                                                       NSURLResponse * _Nullable response, 
                                                                                             NSError * _Nullable error) {
      
              if (error == nil) {
      
                  // 设置下载的文件存储路径
                  NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                                        stringByAppendingPathComponent:response.suggestedFilename];
      
                  // 处理下载的数据
                  [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
              }
          }];
      
          // 执行任务
          [urlSessionDownloadTask resume];
    • 使用 url 协议 方式

          // 遵守协议 <NSURLSessionDownloadDelegate>
      
          // 设置请求路径
          NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
      
          // 创建会话对象
          NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                   delegate:self 
                                                              delegateQueue:[NSOperationQueue mainQueue]];
      
          // 发送请求
          NSURLSessionDownloadTask *urlSessionDownloadTask = [urlSession downloadTaskWithURL:url];
      
          // 执行任务
          [urlSessionDownloadTask resume];
      
          // 协议方法
      
          // 监听下载进度,每当写入数据到临时文件时,就会调用一次这个方法
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                     didWriteData:(int64_t)bytesWritten 
                                                totalBytesWritten:(int64_t)totalBytesWritten 
                                        totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {                    
              // 这次写入多少
              NSLog(@"bytesWritten: %lli", bytesWritten);
      
              // 已经写入的大小
              NSLog(@"totalBytesWritten: %lli", totalBytesWritten);
      
              // 总大小
              NSLog(@"totalBytesExpectedToWrite: %lli", totalBytesExpectedToWrite);
      
              float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
      
              dispatch_async(dispatch_get_main_queue(), ^{
      
                  // 设置下载进度条
                  self.progressView.progress = progress;
              }); 
          }
      
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                        didFinishDownloadingToURL:(NSURL *)location {
      
              // 设置下载的文件存储路径
              NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                   stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
      
              // 处理下载的数据
              [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:nil];
          }
      
          // 恢复下载任务时调用
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                didResumeAtOffset:(int64_t)fileOffset 
                                               expectedTotalBytes:(int64_t)expectedTotalBytes {
      
          }
      
          // 下载完成或中断
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
          }
    • 断点续传下载方式

          // 开始下载
      
              _downloadSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                               delegate:self delegateQueue:
      
              _resumeTmpPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] 
                                                               stringByAppendingPathComponent:@"resumeData.tmp"];
      
              if ([[NSFileManager defaultManager] fileExistsAtPath:self.resumeTmpPath] == NO) {
      
                  // 多次停止下载,下载的临时文件 会 被自动删除
                  NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
      
                  self.downloadTask = [self.downloadSession downloadTaskWithURL:url];
      
                  // 重新开始下载
                  [self.downloadTask resume];
      
              } else {
      
                  self.resumeData = [NSData dataWithContentsOfFile:self.resumeTmpPath];
      
                  // 使用断点下载需要之前下载的临时文件存在,才能继续下载
                  self.downloadTask = [self.downloadSession downloadTaskWithResumeData:self.resumeData];
      
                  // 断点开始下载
                  [self.downloadTask resume];
              }
      
          // 暂停下载
      
              [self.downloadTask suspend];
      
          // 继续下载
      
              [self.downloadTask resume];
      
          // 停止下载
      
              // 停止下载。一旦这个 task 被取消了,就无法再恢复
              [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
      
                  if (resumeData) {
      
                  self.resumeData = resumeData;
      
                  [self.resumeData writeToFile:self.resumeTmpPath atomically:YES];
                  }
      
                  self.downloadTask = nil;
              }];
      
          // 协议方法
      
              - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                         didWriteData:(int64_t)bytesWritten 
                                                    totalBytesWritten:(int64_t)totalBytesWritten 
                                            totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
      
                  float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
      
                  dispatch_async(dispatch_get_main_queue(), ^{
                      [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
                  });
              }
      
              - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                            didFinishDownloadingToURL:(NSURL *)location {
      
                  // 处理下载的数据
                  NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                           stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
                  [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:documentsDirPath error:NULL];
      
                  // 删除断点下载缓存文件
                  [[NSFileManager defaultManager] removeItemAtPath:self.resumeTmpPath error:NULL];
              }
      
              - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                    didResumeAtOffset:(int64_t)fileOffset 
                                                   expectedTotalBytes:(int64_t)expectedTotalBytes {
      
                  NSLog(@"恢复下载,已完成:%f%%", (100.0 * fileOffset / expectedTotalBytes));
              }
      
              - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
                  NSLog(@"didCompleteWithError --- 中断下载: %@", error.userInfo[NSLocalizedDescriptionKey]);
      
                  if (error) {
      
                      // 获取断点数据
                      self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
                  }
      
                  // 下载的临时文件不存在时
                  if ([error.localizedFailureReason isEqualToString:@"No such file or directory"]) {
      
                      // 删除断点下载缓存文件,否则继续断点下载会报错
                      [[NSFileManager defaultManager] removeItemAtPath:self.resumeTmpPath error:nil];
      
                      [self start];
                  }
              }
    • 后台下载方式

          // 配置为后台下载方式
          NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"myBackgroundID"];     
          _downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
      
          NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"]];
      
          [[self.downloadSession downloadTaskWithRequest:request] resume];
      
          // 协议方法
      
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                                     didWriteData:(int64_t)bytesWritten 
                                                totalBytesWritten:(int64_t)totalBytesWritten 
                                        totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
      
              float progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
      
              dispatch_async(dispatch_get_main_queue(), ^{
                  [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
              });
          }
      
          - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                        didFinishDownloadingToURL:(NSURL *)location {
      
              NSString *documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] 
                                                       stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
      
              [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:documentsDirPath error:nil];
          }

6、NSURLSession 文件上传

  • Objective-C

    • 使用 formData block 方式

          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload.php"]];
          urlRequest.HTTPMethod = @"POST";
      
          #define boundary @"uploadBoundary"
      
          // 设置请求头
          [urlRequest setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] 
                       forHTTPHeaderField:@"Content-Type"];
      
          // 设置请求文件参数
      
          NSMutableData *formData = [NSMutableData data];
      
          // 参数
          [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n",@"username"] 
                                                                                  dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"\r\n%@\r\n", @"qian"] dataUsingEncoding:NSUTF8StringEncoding]];
      
          // 文件
          [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; 
                                                                             name=\"%@\"; 
                                                                         filename=\"%@\"\r\n", @"userfile", @"test1.jpg"] 
                                                                                  dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"Content-Type: image/jpeg\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"HQ_0005" ofType:@"jpg"]]];
          [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
          // 结束
          [formData appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
      
          NSURLSession *urlSession = [NSURLSession sharedSession];
      
          NSURLSessionUploadTask *urlSessionUploadTask = [urlSession uploadTaskWithRequest:urlRequest 
                                                                                  fromData:formData 
                                                                         completionHandler:^(NSData * _Nullable data,
                                                                                      NSURLResponse * _Nullable response, 
                                                                                            NSError * _Nullable error) {
      
          }];
      
          [urlSessionUploadTask resume];
    • 使用 formData 协议 方式

          // 遵守协议 <NSURLSessionDataDelegate>
      
          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload.php"]];
          urlRequest.HTTPMethod = @"POST";
      
          #define boundary @"uploadBoundary"
      
          // 设置请求头
          [urlRequest setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] 
                       forHTTPHeaderField:@"Content-Type"];
      
          // 设置请求文件参数
      
          NSMutableData *formData = [NSMutableData data];
      
          // 参数
          [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n",@"username"] 
                                                                                  dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"\r\n%@\r\n", @"qian"] dataUsingEncoding:NSUTF8StringEncoding]];
      
          // 文件
          [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; 
                                                                             name=\"%@\"; 
                                                                         filename=\"%@\"\r\n", @"userfile", @"test2.png"]  
                                                                                   dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[[NSString stringWithFormat:@"Content-Type: image/png\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
          [formData appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"HQ_0011" ofType:@"png"]]];
          [formData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
      
          // 结束
          [formData appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
      
          NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                   delegate:self 
                                                              delegateQueue:[[NSOperationQueue alloc] init]];
      
          NSURLSessionUploadTask *urlSessionUploadTask = [urlSession uploadTaskWithRequest:urlRequest fromData:formData];
      
          [urlSessionUploadTask resume];
      
          // 协议方法
      
          // 监听上传进度
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent 
                                                                                    totalBytesSent:(int64_t)totalBytesSent 
                                                                          totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
      
              float progress = 1.0 * totalBytesSent / totalBytesExpectedToSend;
      
              dispatch_async(dispatch_get_main_queue(), ^{
                  [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
              });
          }
      
          // 接收到服务器的响应
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 
                                           didReceiveResponse:(NSURLResponse *)response 
                                            completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
              /*
                  需要使用 completionHandler 回调告诉系统应该如何处理服务器返回的数据,默认是取消的。
      
                  NSURLSessionResponseCancel = 0,             默认的处理方式,取消
                  NSURLSessionResponseAllow = 1,              接收服务器返回的数据
                  NSURLSessionResponseBecomeDownload = 2,     变成一个下载请求
                  NSURLSessionResponseBecomeStream            变成一个流
              */
      
              completionHandler(NSURLSessionResponseAllow);
      
              // 异步下载数据源初始化
              self.asyncNetData = [[NSMutableData alloc] init];
          }
      
          - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
      
              [self.asyncNetData appendData:data];
          }
      
          - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
          }
    • 单文件上传封装,使用 formData 方式

      • 文件数据封装使用到第三方框架 QExtension,具体实现代码见 GitHub 源码 QExtension
          #import "NSData+FormData.h"
      
          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload.php"]];
          urlRequest.HTTPMethod = @"POST";
      
          NSURL *fileURL = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"HQ_0011" ofType:@"png"]];
          NSData *formData = [NSData q_formDataWithRequest:urlRequest 
                                                      text:@"qian" 
                                                  textName:@"username" 
                                                   fileURL:fileURL 
                                                      name:@"userfile" 
                                                  fileName:nil 
                                                  mimeType:nil];
      
          [[[NSURLSession sharedSession] uploadTaskWithRequest:urlRequest 
                                                      fromData:formData 
                                             completionHandler:^(NSData * _Nullable data, 
                                                          NSURLResponse * _Nullable response, 
                                                                NSError * _Nullable error) {
      
          }] resume];
    • 多文件上传封装,使用 formData 方式

      • 文件数据封装使用到第三方框架 QExtension,具体实现代码见 GitHub 源码 QExtension
          #import "NSData+FormData.h"
      
          NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.88.200/upload/upload-m.php"]];
          urlRequest.HTTPMethod = @"POST";
      
          NSURL *fileURL1 = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"HQ_0005" ofType:@"jpg"]];
          NSURL *fileURL2 = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"HQ_0011" ofType:@"png"]];
          NSData *formData = [NSData q_formDataWithRequest:urlRequest 
                                                     texts:@[@"qian"] 
                                                 textNames:@[@"username"] 
                                                  fileURLs:@[fileURL1, fileURL2] 
                                                      name:@"userfile[]"
                                                 fileNames:nil mimeTypes:nil];
      
          [[[NSURLSession sharedSession] uploadTaskWithRequest:urlRequest 
                                                      fromData:formData 
                                             completionHandler:^(NSData * _Nullable data, 
                                                          NSURLResponse * _Nullable response, 
                                                                NSError * _Nullable error) {
      
          }] resume];
    • PUT Block 方式

          // NSString+Base64.m
      
              @implementation NSString (Base64)
      
              - (NSString *)q_base64Encode {
      
                  NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];               
      
                  return [data base64EncodedStringWithOptions:0];
              }
      
              - (NSString *)q_basic64AuthEncode {
      
                  return [@"BASIC " stringByAppendingString:[self q_base64Encode]];
              }
      
              @end
      
          // ViewController.m
      
              // 本地要上传的文件
              NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"minion.mp4" withExtension:nil];
      
              // 123.mp4 保存到服务器的文件名
              NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/uploads/123.mp4"];
      
              // PUT 文件上传,以文件的方式直接写入到 WebDav 服务器中
              NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
              urlRequest.HTTPMethod = @"PUT";
      
              // 服务器验证,用户访问名和密码
              [urlRequest setValue:[@"admin:adminpasswd" q_basic64AuthEncode] forHTTPHeaderField:@"Authorization"];
      
              [[[NSURLSession sharedSession] uploadTaskWithRequest:urlRequest 
                                                          fromFile:fileURL 
                                                 completionHandler:^(NSData * _Nullable data, 
                                                              NSURLResponse * _Nullable response, 
                                                                    NSError * _Nullable error) {
      
              }] resume];
    • PUT 协议 方式

          // NSString+Base64.m
      
              @implementation NSString (Base64)
      
              - (NSString *)q_base64Encode {
      
                  NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];               
      
                  return [data base64EncodedStringWithOptions:0];
              }
      
              - (NSString *)q_basic64AuthEncode {
      
                  return [@"BASIC " stringByAppendingString:[self q_base64Encode]];
              }
      
              @end
      
          // ViewController.m
      
              // 遵守协议 <NSURLSessionDataDelegate>
      
              // 本地要上传的文件
              NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"minion.mp4" withExtension:nil];
      
              // 123.mp4 保存到服务器的文件名
              NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/uploads/123.mp4"];
      
              // PUT 文件上传,以文件的方式直接写入到 WebDav 服务器中
              NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
              urlRequest.HTTPMethod = @"PUT";                                                                         
      
              // 服务器验证,用户访问名和密码
              [urlRequest setValue:[@"admin:adminpasswd" q_basic64AuthEncode] forHTTPHeaderField:@"Authorization"];
      
              NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
                                                                       delegate:self 
                                                                  delegateQueue:[[NSOperationQueue alloc] init]];
      
              [[urlSession uploadTaskWithRequest:urlRequest fromFile:fileURL] resume];
      
              // 协议方法
      
              // 监听上传进度
              - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 
                                              didSendBodyData:(int64_t)bytesSent 
                                               totalBytesSent:(int64_t)totalBytesSent 
                                     totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
      
                  float progress = 1.0 * totalBytesSent / totalBytesExpectedToSend;
      
                  dispatch_async(dispatch_get_main_queue(), ^{
                      [self.progressBtn q_setButtonWithProgress:progress lineWidth:10 lineColor:nil backgroundColor:[UIColor yellowColor]];
                  });
              }
      
              // 接收到服务器的响应
              - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 
                                               didReceiveResponse:(NSURLResponse *)response 
                                                completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
                  /*
                      需要使用 completionHandler 回调告诉系统应该如何处理服务器返回的数据,默认是取消的。
      
                      NSURLSessionResponseCancel = 0,             默认的处理方式,取消
                      NSURLSessionResponseAllow = 1,              接收服务器返回的数据
                      NSURLSessionResponseBecomeDownload = 2,     变成一个下载请求
                      NSURLSessionResponseBecomeStream            变成一个流
                  */
      
                  completionHandler(NSURLSessionResponseAllow);
              }
      
              - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
      
              }
      
              - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
      
              }
目录
相关文章
|
6月前
|
缓存 iOS开发
IOS网络编程:使用 URLSession 实现网络请求的步骤是什么?
IOS网络编程:使用 URLSession 实现网络请求的步骤是什么?
101 1
|
3月前
|
安全 网络安全 Android开发
安卓与iOS开发:选择的艺术网络安全与信息安全:漏洞、加密与意识的交织
【8月更文挑战第20天】在数字时代,安卓和iOS两大平台如同两座巍峨的山峰,分别占据着移动互联网的半壁江山。它们各自拥有独特的魅力和优势,吸引着无数开发者投身其中。本文将探讨这两个平台的特点、优势以及它们在移动应用开发中的地位,帮助读者更好地理解这两个平台的差异,并为那些正在面临选择的开发者提供一些启示。
119 56
|
6月前
|
移动开发 网络协议 安全
iOS审核在ipv6网络下无法访问服务器的问题及解决方案
iOS审核在ipv6网络下无法访问服务器的问题及解决方案
172 0
|
6月前
|
XML JSON API
IOS网络编程:介绍一下 Alamofire 库。
IOS网络编程:介绍一下 Alamofire 库。
144 3
|
6月前
|
缓存 JSON API
IOS网络编程:什么是 RESTful API?如何使用 RESTful 风格设计 API?
IOS网络编程:什么是 RESTful API?如何使用 RESTful 风格设计 API?
155 3
|
iOS开发
iOS开发 GET、POST请求方法:NSURLSession篇
iOS开发 GET、POST请求方法:NSURLSession篇
102 0
|
网络协议 安全 数据库
第二章TCP/IP—iOS网络七层模型
分层: 应用层 (Application): 网络服务与最终用户的一个接口。 协议有:HTTP FTP TFTP SMTP SNMP DNS 表示层(Presentation Layer): 数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层) 格式有,JPEG、ASCll、DECOIC、加密格式等 会话层(Session Layer): 建立、管理、终止会话。(在五层模型里面已经合并到了应用层) 对应主机进程,指本地主机与远程主机正在进行的会话 传输层 (Transport): 定义传输数据的协议端口号,以及流控和差错效验。
510 0
第二章TCP/IP—iOS网络七层模型
|
安全 定位技术 开发工具
IOS9中的一些网络配置
iOS9为了增强数据访问安全,将所有的http请求都改为了https,为了能够在iOS9中正常使用地图SDK,请在"Info.plist"中进行如下配置,否则影响SDK的使用。
69 0