NSURLSession 网络请求

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 的设置

  • 2.1 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"];
  • 2.2 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};
  • 2.2 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]];
  • 2.3 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];
    • 2.4 Task 的设置

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

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

    #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 数据请求

  • 3.1 使用 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];
  • 3.2 使用 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);
        }   
    }
  • 3.3 使用 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];
  • 3.4 使用 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 数据请求

  • 4.1 使用 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];
  • 4.2 使用 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 文件下载

  • 5.1 使用 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];
  • 5.2 使用 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 {
    
    }
  • 5.3 使用 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];
  • 5.4 使用 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 {
    
    }
  • 5.5 断点续传下载方式

    // 开始下载
    
    _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];
        }
    }
  • 5.6 后台下载方式

    // 配置为后台下载方式
    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 文件上传

  • 6.1 使用 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];
  • 6.2 使用 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 {
    
    }
  • 6.3 单文件上传封装,使用 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];
  • 6.4 多文件上传封装,使用 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];
  • 6.5 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];
  • 6.6 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 {
    
    }

转载于:https://www.cnblogs.com/CH520/p/9451976.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值