OC之NSURLsession

iOS App的网络请求,无论是从服务器请求数据还是下载文件到本地,都使用 Https 标准协议或自定义协议提供对URL标识的资源的访问!苹果提供了 NSURLSession 来处理复杂的通信任务!

NSURLSession 是对 NSURLConnection 的替代,是网络通信的管理者,协调一组相关的网络数据传输任务,请求是高度异步的。可以创建一个或多个 NSURLSession 实例将服务器数据提取并返回到App、下载文件或和文件上传到服务器,也支持身份验证、接收HTTP重定向等事件;当 App 挂起时,支持后台下载。

之所以说它是网络通信的管理者,是因为NSURLSession 协调一组相关类完成网络通信:

  • NSURLSessionConfiguration :配置选项的封装,如与主机同时连接的最大并发数目、使用的多路径TCP策略、以及是否允许蜂窝网络, 请求缓存策略, 请求超时, cookies/证书存储等等;
  • NSURLSessionDelegate : 用于处理响应数据的代理;
  • NSURLSessionTask : 通过请求创建的任务;
  • NSURLSessionTaskMetrics :对发送请求/DNS查询/TLS握手/请求响应等各种环节时间上的统计. 可用于分析App的请求缓慢到底是发生在哪个环节, 并对此优化APP性能。
  • NSURLSessionTaskTransactionMetrics
NSURLSession相关类关系图.png

完成一个网络通信的流程为:

//step1 :配置一些选项
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
//step2:设置处理响应数据的队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//step3:创建 session
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];
//step4:利用 session 创建任务
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@""]];
//step5:开始任务
[task resume];//刚创建出来的task默认是挂起状态的,需要调用该方法来启动任务(执行任务)

1、创建 NSURLSession

NSURLSession API 提供了三种方法用来实例化:

/** 全局共享单例
 * 有很大局限性:
 *  <li> 没有设置 delegate,因此不会调用代理方法;
 *  <li> 没有定制 configuration 用于基本请求;
 *  <li> 当收到服务器的响应报文时,不能增量地获取数据;
 *  <li> 无法对默认连接行为进行定制;
 *  <li> 执行身份验证的能力是有限的;
 *  <li> App 挂起时,不能执行后台下载或上传。
 * sharedSession 使用全局 NSURLCache、NSHTTPCookieStorage、NSURLCredentialStorage
 * @note 如果使用缓存、cookie、身份验证或自定义网络协议进行任何操作,应该使用默认会话而不是共享会话。
 * @note :不管 session 执行的线程为主线程还是子线程,completionHandler 代码均在任意子线程执行。
*/
@property (class, readonly, strong) NSURLSession *sharedSession;

/** 根据指定的 Configuration 创建一个网络会话
 * 由于没有设置 delegate ,因此不会调用代理方法;
 * completionHandler 中的代码均在任意子线程执行
 */
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;


/** 使用指定的会话配置,委托和操作队列创建会话
 * 设置了 delegate,因此期望响应数据通过代理方式处理;但是在创建Task的时候,若使用参数 completionHandler ,则响应仍然会在completionHandler 中处理,而非代理方法。因此,若保证使用代理方式处理,则需将 completionHandler 设置为nil 。
 * @note   会话对象保存对 delegate 的强引用,直到应用程序退出或显式地使会话无效为止。如果不使会话无效,App 就会泄露内存,直到它退出。
*/
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;

创建 NSURLSession 时几个关键的参数,需要说明一下:

  • 代理delegate:可以设置一个 delegate,在会话生命周期内接收响应报文,处理身份验证等事件;
    也可以 delegate=nil 使用 completionHandler 来处理服务器的响应报文;
  • queue:用来处理响应数据的线程,
    若为 mainQueue,则代理方法或者 completionHandler 中的代码在主线程程执行;
    若为 nil 或者创建一个操作队列,则在任意子线程执行;
  • configuration:对会话一些配置的封装:如使用CacheCookie、证书,或者是否允许在蜂窝网络上进行连接!

NSURLSessiondelegatequeue持有强引用,为避免内存泄漏,需要显式地使会话无效!

NSURLSession 实例是线程安全的:可以在任何线程中创建会话和任务;当代理方法调用时,将在正确的委托队列上调用。

注意:只能使用上述方法获取一个 NSURLSession 对象,禁止使用 -init+new等方法实例化;

eg、错误的创建方法

虽然 -init 编译时没报错,但在运行时发送一个请求会出错:

{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunes_URL]];
    NSURLSession *session = [[NSURLSession alloc] init];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { }];
    [dataTask resume];
    
/** 异常终止的部分信息:
-[NSURLSession dataTaskForRequest:completion:]: unrecognized selector sent to instance 0x1702007f0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSURLSession dataTaskForRequest:completion:]: unrecognized selector sent to instance 0x1702007f0'
*** First throw call stack:
(0x1836aefe0 0x182110538 0x1836b5ef4 0x1836b2f54 0x1835aed4c 0x1000fe458 0x10011edec 0x18990ba9c 0x1899bb820 0x189a6d594 0x189a5f630 0x1897d328c 0x18365c9a8 0x18365a630 0x18365aa7c 0x18358ada4 0x184ff5074 0x189845c9c 0x10012fe70 0x18259959c)
libc++abi.dylib: terminating with uncaught exception of type NSException
 */
}

当向方法传送非法参数时引发的异常 NSInvalidArgumentException ,这是由于没有配置 configuration 属性。

2、NSURLSession 的一些属性

/**  操作队列:需要在创建此对象时提供
 * 作用域:与 NSURLSession 相关的所有代理方法调用和 completionHandler 都在这个队列上执行;
 * @note 在 App 退出或 NSURLSession 被释放之前,session 对该队列保持强引用;为避免内存泄漏,需要使会话无效。
 */
@property (readonly, retain) NSOperationQueue *delegateQueue;

/** 委托代理:需要在创建此对象时设置,负责处理身份验证挑战、缓存以及处理其它与会话相关的事件
 * @note 会话对象对该委托具有强引用,为避免内存泄漏,需要显式地使会话无效;
 */
@property (nullable, readonly, retain) id <NSURLSessionDelegate> delegate;

/** 一些配置选项:需要在创建此对象时设置
 * @note 在iOS9之前,由于不是拷贝的副本,允许在初始化后通过修改 Configuration 的某些属性来进一步配置会话行为,这是一个 bug;
 *       从iOS9开始,是入参的拷贝副本,以便会话的配置在初始化后不被影响!
*/
@property (readonly, copy) NSURLSessionConfiguration *configuration;

/** 用于调试程序的描述性标签,默认为nil
 */
@property (nullable, copy) NSString *sessionDescription;

3、管理会话

/** 完成任务并将 NSURLSession 置为无效!
 * 异步方法,会立即返回,此时 NSURLSession 需要等待现有任务完成后才会无效,但新的任务不被创建;
 * 代理方法继续执行,直到 -URLSession:didBecomeInvalidWithError: 执行,NSURLSession 无效。
 * @note sharedSession 调用该方法没有任何影响。
 */
- (void)finishTasksAndInvalidate;

/** 将 NSURLSession 置为无效,向此会话中所有未完成的任务发出 -cancel;但新的任务不被创建;
 * @note: 任务取消取决于任务的状态,有些任务在发送 -cancel 时可能已经完成。
 * @note sharedSession 调用该方法没有任何影响。
 */
- (void)invalidateAndCancel;

 /** 清空所有 Cookie、Cache 和证书,删除磁盘文件,将正在进行的下载刷新到磁盘,并确保将来的请求发生在新的 socket上。
  * @param completionHandler 当 reset 操作完成时被调用,handler 在委托队列上执行。
  */
- (void)resetWithCompletionHandler:(void (^)(void))completionHandler;

/** 将Cookie和证书刷新到磁盘,清除临时缓存,并确保将来的请求发生在新的TCP连接上。
 * @param completionHandler 当 reset 操作完成时被调用,handler 在委托队列上执行。
 */
- (void)flushWithCompletionHandler:(void (^)(void))completionHandler;

/** 对会话中创建的未完成的 dataTasks、上传和下载任务调用 completionHandler
 * @param completionHandler 要使用任务列表调用,在委托队列上执行;不包括完成、失败或被取消后无效的任何任务。
 */
- (void)getTasksWithCompletionHandler:(void (^)(NSArray<NSURLSessionDataTask *> *dataTasks, NSArray<NSURLSessionUploadTask *> *uploadTasks, NSArray<NSURLSessionDownloadTask *> *downloadTasks))completionHandler;

/** 获取会话中的所有任务
 * @param completionHandler 要使用任务列表调用
 */
- (void)getAllTasksWithCompletionHandler:(void (^)(NSArray<__kindof NSURLSessionTask *> *tasks))completionHandler API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0));

4、向会话添加任务

在网络通信中,NSURLSession根据请求NSURLRequest可以创建多种任务

  • NSURLSessionDataTask:数据任务,使用NSData对象发送和接收数据;数据任务旨在向服务器发出简短的,经常是交互式的请求;支持默认会话、临时会话,但不支持后台会话;
    通过对代理方法 -URLSession:dataTask:didReceiveData: 的一系列调用来接收资源;该任务供使用者立即解析。
  • NSURLSessionUploadTask :上传任务,与数据任务相似,但是它们还发送数据(通常以文件形式),并在应用程序不运行时支持后台上传;
    通过引用要上传的文件或数据对象,或利用 -URLSession:task:needNewBodyStream: 来提供上传主体显式创建的;与数据任务的区别在于实例构造方式不同!
  • NSURLSessionDownloadTask :下载任务,直接将响应数据写入临时文件,任何类型的会话都支持下载和上传任务。
    任务完成后,delegate 调用 -URLSession:downloadTask:didFinishDownloadingToURL: 在适当时机将该文件移动到沙盒的永久位置、或者读取该文件;
    如果取消任务,NSURLSessionDownloadTask 可以生成一个 data blob,用于稍后恢复下载。
  • NSURLSessionWebSocketTaskWebSocket任务,使用 RFC 6455 中定义的WebSocket协议通过TCP和TLS交换消息。
  • NSURLSessionStreamTask:从 iOS9 开始支持该任务,这允许TCP/IP连接到指定的主机和可选的安全握手和代理导航的端口;
NSURLSessionTask.png

通过NSURLSession 创建任务,有两种响应方式:

  • 设置 delegate:响应报文被 NSURLSessionDelegate 的代理方法处理;
  • 使用 completionHandler 创建任务,那么在 completionHandler 中处理响应数据(即使设置了 delegate);

如果设置了delegate,根据不通的任务,由不同的 NSURLSessionDelegate 方法来处理:

  • NSURLSessionDelegate : 是 session 级别的协议,主要管理 session 的生命周期、处理证书认证等
  • NSURLSessionTaskDelegate : 是 task 级别的协议,面向所有的委托方法
  • NSURLSessionDataDelegate : 是 task 级别的协议,主要用来处理 data 和 upload,如接收到响应,接收到数据,是否缓存数据
  • NSURLSession​Download​Delegate : 是 task 级别的协议,用来处理下载任务
  • NSURLSessionStreamDelegate : 是 task 级别的协议,用来处理 streamTask
  • NSURLSessionWebSocketDelegate: 是 task 级别的委托,处理特定于 WebSocketTask 的事件
NSURLSessionDelegate.png

可以重复使用一个NSURLSession来创建多个任务,创建的 NSURLSessionTask 对象总是处于挂起状态,在它们执行之前必须调用 -resume 方法。

4.1、向会话中添加 DataTasks
/** 使用指定的 NSURLRequest 创建一个数据任务
 * @param 请求可以有一个 body stream
*/
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;

/** 使用指定的 URL 创建一个数据任务
 */
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

/** 使用指定的 NSURLRequest 创建一个数据任务
 * @param completionHandler 任务完成时调用;绕过正常的代理调用响应和数据传递;
 *          如果设置了 delegate,在 authentication challenges 仍然会被调用;
 *          该参数传递 nil,任务完成时调用代理方法,此时等同于 -dataTaskWithRequest: 方法
 */
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

/** 使用指定的 URL 创建一个数据任务,提供一个简单的可取消异步接口来接收数据。
 * @param completionHandler 任务完成时调用;绕过正常的代理调用响应和数据传递;
 *          如果设置了 delegate,在 authentication challenges 仍然会被调用;
 *          该参数传递 nil,任务完成时调用代理方法,此时等同于 -dataTaskWithRequest: 方法
 */
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
4.2、向会话中添加 DownloadTasks

当下载成功完成时,需要将下载数据从临时文件拷贝至指定文件,临时文件将被自动删除。

/** 使用指定的 NSURLRequest 创建一个下载任务
 */
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

/** 使用指定的 url 创建一个下载任务
 */
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

/** 使用 resume Data 创建一个下载任务,以恢复先前取消或失败的下载
 * @resumeData 提供恢复下载所需的数据对象
 * @note 如果下载不能恢复,将调用 -URLSession:task:didCompleteWithError:
 */
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
4.3、向会话中添加 UploadTasks
/** 使用指定的 NSURLRequest 创建一个上传任务
 * @request 上传任务的请求包含一个请求体以上传元数据,比如POST或PUT请求。
 * @param fileURL 待上载的文件的URL
 */
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

/** 使用指定的 NSURLRequest 创建一个上传任务
 * @param bodyData 请求体的元数据由 bodyData 提供
 */
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

/** 使用指定的 NSURLRequest 创建一个上传任务
 * @note 必须由代理方法 -URLSession:task:needNewBodyStream: 提供上传的元数据
 */
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
4.4、向会话中添加 StreamTasks
/** 创建一个 StreamTask,该任务建立指定主机名和端口的双向TCP/IP连接
 * @param hostname 主机名
 * @param 端口
*/
- (NSURLSessionStreamTask *)streamTaskWithHostName:(NSString *)hostname port:(NSInteger)port API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0));

/** 使用指定的 NSNetService 创建双向TCP/IP连接的 streamTask
 * @param service 用于确定TCP/IP连接端点的NSNetService对象;在将任何数据读取或写入结果的streamTask 之前解析此网络服务。
*/
- (NSURLSessionStreamTask *)streamTaskWithNetService:(NSNetService *)service API_AVAILABLE(macos(10.11), ios(9.0), tvos(9.0)) API_UNAVAILABLE(watchos);
4.5、向会话中添加 WebSocketTasks
/** 使用指定的 URL 创建一个 WebSocket 任务
 * @param url 要连接 WebSocket 的 URL,必须有一个ws或wss方案;
*/
- (NSURLSessionWebSocketTask *)webSocketTaskWithURL:(NSURL *)url API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

/** 根据指定的 URL 和协议数组,创建一个WebSocket任务
 * @param url 要连接 WebSocket 的 URL
 * @param protocols 与服务器进行协商的协议数组;这些协议将在WebSocket握手中用于与服务器协商一个优先的协议
*/
- (NSURLSessionWebSocketTask *)webSocketTaskWithURL:(NSURL *)url protocols:(NSArray<NSString *>*)protocols API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

/** 使用指定的 NSURLRequest 创建一个WebSocket任务
 * 可以在调用 -resume 之前修改请求的属性,该任务在 HTTP 握手阶段使用这些属性。
 * 要添加自定义协议,请添加一个带有 Sec-WebSocket-Protocol的 HTTP headers,以及一个以逗号分隔的要与服务器协商的协议列表。
 * 客户端提供的 HTTP headers 在与服务器握手时将保持不变。
*/
- (NSURLSessionWebSocketTask *)webSocketTaskWithRequest:(NSURLRequest *)request API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

5、 使用 NSURLsession 完成一个网络通信

5.1、使用 completionHandler 方式创建一个 Get 请求
5.1.1、 使用 sharedSession 单例创建会话:

创建了一个简单的 Get 请求, sharedSession 默认配置类,代理对象与操作队列默认为nil,来看下会话的回调结果:

{
    //注意:NSURLRequest 默认是 GET 请求
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunes_URL]];
    NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"currentThread : %@",[NSThread currentThread]);
        if (error){
            NSLog(@"请求失败:%@",error);
        }else{
            NSLog(@"请求成功");
        }
    }];
    [dataTask resume];
}

/** 打印日志
currentThread : <NSThread: 0x174263080>{number = 5, name = (null)}
请求成功
*/

这个会话成功的收到响应,而且响应的回调为任意分线程,这时如果要更新 UI ,就要回到主线程去!

5.1.2、配置 session 时,不设置 delegate

创建了一个简单的 Get 请求,为 session 设置了配置类,代理对象与操作队列默认为 nil,来看下会话的回调结果:

{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunes_URL]];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"currentThread : %@",[NSThread currentThread]);
        if (error){
            NSLog(@"请求失败:%@",error);
        }else{
            NSLog(@"请求成功");
        }
    }];
    [dataTask resume];
/** 打印日志
currentThread : <NSThread: 0x174263170>{number = 8, name = (null)}
请求成功
*/
}

这个会话成功的收到响应,而且响应的回调为任意分线程!

5.1.3、配置 session 时,设置 delegate ,设置 delegateQueue

创建了一个简单的 Get 请求,为 session 设置了配置类,代理对象,操作队列,来看下会话的回调结果:

{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunes_URL]];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"currentThread : %@",[NSThread currentThread]);
        if (error){
            NSLog(@"请求失败:%@",error);
        }else{
            NSLog(@"请求成功");
        }
    }];
    [dataTask resume];
 
/** 打印日志:
currentThread : <NSThread: 0x17006c740>{number = 1, name = main}
请求成功
*/
}
注意:内存泄露

使用 Instruments 监控了以上请求的内存情况,发现除了 sharedSession 方式配置的 session ,其余的方式创建 task 都存在内存泄露:

Instruments结果.png

这是为什么呢?还记得我们前文强调的嘛:

会话对象保存对委托的强引用,直到应用程序退出或显式地使会话无效为止。如果你不使会话无效,你的应用程序就会泄露内存,直到它退出。
也就是说:如果我们不调用以下两个方法中的一个使 session 失效,session 是会内存泄露的。

5.2、使用 completionHandler 方式创建一个 Post 请求
5.2.1、创建Post 请求下载图片

使用 session 创建了一个简单的下载图片的 downloadTask,下载成功后将文件从临时路径转移到我们指定的位置

{
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSString *imagePath = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528867244313&di=904a1b5eb7db534ea15ce4c266bfa1c4&imgtype=0&src=http%3A%2F%2Fpic.58pic.com%2F58pic%2F15%2F36%2F01%2F58PIC2958PICbAX_1024.jpg";
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imagePath]];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"currentThread : %@",[NSThread currentThread]);
        if (error){
            NSLog(@"请求失败:%@",error);
        }else{
            NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
            NSString *newFilePath = [documentsPath stringByAppendingPathComponent:response.suggestedFilename];
            [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:newFilePath error:nil];
            NSLog(@"请求成功:%@",newFilePath);
        }
    }];
    [downloadTask resume];
}
5.2.2、创建Post 请求上传一个图片

上传一个文件时,需要在请求头添加 Content-Type ,设置边界 boundary 为任意值,有兴趣的可以去了解下 HTTP协议

{
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:queue];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"updateFile"]];
    request.HTTPMethod = @"POST";
    [request setValue:@"multipart/form-data;boundary=***" forHTTPHeaderField:@"Content-Type"];
    NSData *imageData = UIImageJPEGRepresentation([UIImage imageNamed:@"myBack"], 0.5);
    NSMutableData *bodyData = [NSMutableData dataWithData:imageData];
    NSURLSessionUploadTask *dask = [session uploadTaskWithRequest:request fromData:bodyData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"currentThread : %@",[NSThread currentThread]);
        if (error){
            NSLog(@"请求失败:%@",error);
        }else{
            NSLog(@"请求成功");
        }
    }];
    [dask resume];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269