文章目录
网络请求再学习
网络请求的步骤
- 创建请求地址
确定接口地址,处理路径和查询参数(如 ?key=xxx&city=beijing)。
-
创建请求类
-
创建会话
- 配置会话
创建
NSURLSessionConfiguration,设置超时时间、缓存策略、是否允许蜂窝网络等。这一步决定了网络请求的"基调"(默认、临时、后台)。- 创建会话
利用上一步的配置,生成
NSURLSession实例。如果不需要代理监听(如进度),直接用[NSURLSession sharedSession]即可。 -
生成任务
根据业务需求选择任务类型(DataTask / DownloadTask / UploadTask),并传入 URL 或 Request 对象。此时任务处于 suspended(挂起)状态,还未开始联网。
- 启动任务
调用 [task resume]
- 处理响应数据
如果自定义了 delegate 且会话不再使用,需调用 [session finishTasksAndInvalidate] 或 [session invalidateAndCancel] 来打破 session 对 delegate 的强引用。
- 清理会话
创建请求地址
在开始学习的时候, 我们创建URL一般是直接使用字符串, 这种方式只适合静态地址 (没有中文, 没有空格, 没有特殊符号)
NSURL *url = [NSURL URLWithString:@"https://api.weatherapi.com/v1/forecast.json"];
创建请求类
在 NSURLSession 中,苹果提供了一个非常便捷的方法:
objc
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
如果你直接传入 URL,系统会在内部自动帮你创建一个默认的 NSURLRequest (默认使用 GET 方法,默认超时时间,默认缓存策略)。在这种情况下,不需要写 NSURLRequest 的代码,任务就直接生成了
通过这种方式直接生成任务就不用去专门创建请求类了
完整步骤 (显式创建)
如果请求稍微复杂一点(比如 POST 请求、需要加 Headers (如 Token)、需要上传 Body 数据),你就必须 显式地创建 NSMutableURLRequest。
objc
// 创建请求类
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
// HTTP 方法
request.HTTPMethod = @"GET";
// 时间阈值
request.timeoutInterval = 15;
创建会话
在iOS中, 创建会话共有3种方式,分别对应不同的业务复杂度
1. 共享单例会话
直接使用系统提供的全局单例对象。
NSURLSession *session = [NSURLSession sharedSession];
- 适用场景:简单的、一次性的 GET/POST 请求,且不需要监听上传/下载进度。
- 特点 :使用系统默认配置(类似浏览器),无法自定义超时时间、缓存策略 ,且无法设置 Delegate 。所有回调只能通过
completionHandler(闭包)处理。 - 内存管理:无需管理,它是个单例,App 生命周期内一直存在。
2. 基于配置的会话(无代理,仅闭包回调)
使用 NSURLSessionConfiguration 来创建,但不设置代理 (传入 nil),所有的数据返回都靠 Block 闭包。
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.timeoutIntervalForRequest = 30.0;
// 注意:delegate 传 nil,delegateQueue 传 nil 或主队列
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
- 适用场景:需要自定义超时、缓存策略或额外 Headers,但不需要监听进度(如普通的 JSON 接口请求)。
- 特点 :灵活度高于
sharedSession,但由于没有 Delegate,无法处理需要身份验证(如客户端证书)或重定向的复杂场景。 - 内存管理 :因为
delegate是nil,不存在循环引用问题,会话用完后系统会自动释放,通常不需要手动 invalidate。
NSURLSession* session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration]];相当于
objcNSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
方法的第三个参数 delegateQueue 指的是回调队列
- 含义 :指定所有代理方法(以及
completionHandler闭包)在哪个线程执行。- 作用 :传入主队列(Main Queue),意味着回调闭包会在主线程执行。
虽然
delegate传的是nil,但这个delegateQueue依然有效。当使用带completionHandler的dataTaskWithRequest:时,这个闭包会在指定的delegateQueue上被调用。因为传了mainQueue,所以成功/失败闭包直接运行在主线程,所以可以安全地在闭包内部更新 UI(如self.label.text = ...),不需要再手动dispatch_async(dispatch_get_main_queue())。
3. 基于配置 + 自定义代理(全功能模式)
这是最强大、最复杂的模式
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
- 适用场景 :大文件下载(需要进度条)、大文件上传、需要处理 HTTPS 证书校验(
didReceiveChallenge)、需要拦截服务器重定向。 - 特点 :可以实时接收到
NSURLSessionDelegate、NSURLSessionDownloadDelegate等协议中的回调。 - 内存管理:会话对象会强引用(retain)代理对象
self。如果self是 ViewController,这就会造成循环引用 ,导致页面dealloc不被调用,造成内存泄漏。
生成任务
任务的类型
| 任务类型 | 创建方法前缀 | 核心特征 | 适用场景 |
|---|---|---|---|
| NSURLSessionDataTask | dataTaskWith... |
数据一次性加载到 内存(NSData) 中返回。 | 调用 JSON 接口、获取图片二进制数据(小图)。 |
| NSURLSessionUploadTask | uploadTaskWith... |
专门负责把数据发送给服务器,支持后台上传。 | 提交头像、上传日志文件、表单提交。 |
| NSURLSessionDownloadTask | downloadTaskWith... |
数据直接写入磁盘临时文件(NSURL),不占用 App 内存。支持断点续传。 | 下载视频、更新包、大体积 ZIP 文件。 |
| NSURLSessionStreamTask | streamTaskWith... |
建立 TCP/IP 流式连接,读写数据按流处理。 | 即时通讯(IM)、WebSocket 长连接。 |
笔者目前还处于初始学习过程, 只接触到 NSURLSessionDataTask
任务的回调方式
闭包(Block) 和 代理 (delegate)
| 回调方式 | 方法特征 | 优点 | 缺点 / 限制 |
|---|---|---|---|
| Block 闭包 (Completion Handler) | 方法名末尾带 completionHandler: 参数。 |
代码紧凑 ,发起和处理的逻辑写在一起,易于阅读。 无循环引用风险(若会话无代理,则无需关心内存泄漏)。 | 无法获取中间进度(DownloadTask 除外,它有单独的进度闭包)。 无法处理服务器要求客户端证书(Authentication Challenge)。 无法拦截重定向。 |
| Delegate 代理 (响应协议方法) | 创建会话时传入 delegate:self,并实现 <NSURLSessionDelegate> 等协议。 |
精细控制 :可以实时监听 didWriteData(进度)、didReceiveChallenge(证书校验)、willPerformHTTPRedirection(重定向)。 分片接收数据 (在 didReceiveData 中手动拼接)。 |
代码分散 ,逻辑割裂在多个回调方法中。 极易造成循环引用 ,必须手动调用 invalidateAndCancel 断开强引用。 |
任务的创建方式
| 输入方式 | 方法名称特征 | 能否设置 Method | 能否设置 Headers/Body | 适用场景 |
|---|---|---|---|---|
传入 NSURL |
dataTaskWithURL: downloadTaskWithURL: |
固定 GET | 不能 | 简单的 GET 请求,无需携带 Token 或特殊头部。 |
传入 NSURLRequest |
dataTaskWithRequest: uploadTaskWithRequest: |
自由设置(POST/PUT/DELETE) | 能(通过 setValue:forHTTPHeaderField: 和 HTTPBody) |
需要登录态(Authorization)、需要传 JSON Body 的 POST 请求。 |
objc
// 创建请求地址
NSString* str = @"https://your-api.com/login?username=testuser&password=123456";
NSURL* url = [NSURL URLWithString: str];
// 创建请求类
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
// HTTP 方法
request.HTTPMethod = @"GET";
// 时间阈值
request.timeoutInterval = 15;
// 配置会话
// 使用默认配置
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 创建会话
NSURLSession* session = [NSURLSession sessionWithConfiguration: config];
// 通过 Request 创建
NSURLSessionDataTask* task = [session dataTaskWithRequest: request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"请求失败: %@", error);
} else {
NSError* jsonError = nil;
// 调用苹果原生 API 将服务器返回的二进制数据 (NSData) 转换为 Objective-C对象
// 传入错误指针的地址, 如果二进制数据不是合法的 JSON 格式(比如缺了一个引号,或者是 HTML 报错页),jsonError 就会被赋值为一个包含具体错误原因的对象,而方法本身会返回 nil。
id dict = [NSJSONSerialization JSONObjectWithData: data options:0 error: &jsonError];
if (jsonError) {
NSLog(@"JSON 解析失败");
} else {
NSLog(@"%@", dict);
}
}
}];
//启动任务
[dataTask resume];
这是通过 requset 和 闭包 (Block) 回调的方式创建task, 还可以采用一个 [session dataTaskWithURL:url completionHandler:]的方式去创建一个,但是如果以后面的方式去创建的话,就意味着是以一种GET方式去获取网络请求,这样只能满足一些简单的网络请求,而使用 request 方式来获取数据可以满足更复杂的情况。
所以我们尽量采用request方法来获取网络请求,采用request的方式可以满足更多需求
下面是通过 URL 创建任务
objc
// 创建地址
NSURL *url = [NSURL URLWithString:@"https://api.example.com/public/data"];
// 配置会话
// 使用默认配置
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 创建会话
NSURLSession* session = [NSURLSession sessionWithConfiguration: config
delegate: nil
delegateQueue: [NSOperationQueue mainQueue]];
// 创建任务(直接传入 URL)
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"请求失败: %@", error);
} else {
NSError* jsonError = nil;
// 调用苹果原生 API 将服务器返回的二进制数据 (NSData) 转换为 Objective-C对象
// 传入错误指针的地址, 如果二进制数据不是合法的 JSON 格式(比如缺了一个引号,或者是 HTML 报错页),jsonError 就会被赋值为一个包含具体错误原因的对象,而方法本身会返回 nil。
id dict = [NSJSONSerialization JSONObjectWithData: data options:0 error: &jsonError];
if (jsonError) {
NSLog(@"JSON 解析失败");
} else {
NSLog(@"%@", dict);
}
}
}];
// 启动任务
[task resume];
通过URL创建任务就不用显式的去创建请求类
启动任务
启动任务的唯一正确方法: [task resume]
刚刚创建好的任务, 处于 suspended (挂起) 的状态 , 它虽然存在, 但是不会发出任何网络请求