iOS开发基础44-网络编程之NSURLSession&AFN
iOS 网络请求三种方式:NSURLConnection、NSURLSession 与 AFNetworking
本文系统梳理 iOS 网络请求的三种方式:NSURLConnection(已废弃,了解)、NSURLSession(苹果推荐,含三种任务类型与断点续传)、AFNetworking(基于 NSURLSession 的第三方库,含序列化与网络状态检测),以及 Swift 对应的 Alamofire。
一、NSURLConnection(已废弃,了解)
NSURLConnection 是 iOS 早期的网络 API,iOS 9 起被 NSURLSession 取代,新项目不推荐使用。
1. 代理方式请求
@interface MyViewController () <NSURLConnectionDataDelegate>
@property (nonatomic, strong) NSMutableData *receivedData;
@end
@implementation MyViewController
- (void)startRequest {
NSURL *url = [NSURL URLWithString:@"https://example.com/api"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// startImmediately:YES 时自动开始,无需手动 start
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.receivedData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"请求完成: %@", [[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding]);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"请求失败: %@", error.localizedDescription);
}
@end
2. 线程与 RunLoop
NSURLConnection 的回调默认在创建 connection 的线程的 RunLoop 中执行(不是固定在主线程)。在主线程创建时回调在主线程;在子线程创建时,子线程必须有 RunLoop 才能接收回调。
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com"]];
// startImmediately:NO,手动调度到当前 RunLoop
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
// 保持 RunLoop 运行(否则线程立即退出,回调无法执行)
[[NSRunLoop currentRunLoop] run];
});
子线程默认没有 RunLoop,
[NSRunLoop currentRunLoop]第一次调用时创建,但run会阻塞当前线程。子线程执行网络请求推荐用 NSURLSession(自动管理线程),无需手动处理 RunLoop。
二、NSURLSession(推荐)
NSURLSession 是 iOS 7+ 引入的现代网络 API,取代 NSURLSession,支持后台下载、任务管理、断点续传、配置灵活。
1. 创建 Session
// 共享 session(基本配置,无法设置代理)
NSURLSession *sharedSession = [NSURLSession sharedSession];
// 自定义配置 session
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.timeoutIntervalForRequest = 15; // 请求超时
config.timeoutIntervalForResource = 30; // 资源超时
config.HTTPAdditionalHeaders = @{@"User-Agent": @"MyApp/1.0"};
// delegateQueue:nil 时,代理方法在子线程串行队列执行;传 mainQueue 则在主线程
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
Session 配置类型:
| 配置 | 说明 |
|---|---|
defaultSessionConfiguration |
默认配置,磁盘缓存 + 凭证存储 |
ephemeralSessionConfiguration |
临时配置,不写磁盘(内存缓存),类似无痕浏览 |
backgroundSessionConfigurationWithIdentifier: |
后台配置,App 退到后台仍可下载/上传 |
2. 三种任务类型
| 任务类型 | 类 | 说明 | 数据去向 |
|---|---|---|---|
| Data Task | NSURLSessionDataTask |
普通请求,获取 JSON/XML 等小数据 | 内存(NSData) |
| Download Task | NSURLSessionDownloadTask |
文件下载,支持断点续传和后台下载 | 临时文件(磁盘) |
| Upload Task | NSURLSessionUploadTask |
文件上传,支持后台上传 | 从文件/NSData 读取 |
3. Data Task(普通请求)
NSURL *url = [NSURL URLWithString:@"https://api.example.com/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"请求失败: %@", error);
return;
}
// 解析数据(completionHandler 在子线程执行,UI 更新需回主线程)
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"结果: %@", json);
});
}];
[task resume]; // 任务默认挂起,必须 resume
4. 任务控制
[task resume]; // 开始/继续
[task suspend]; // 暂停(挂起)
[task cancel]; // 取消
cancelByProducingResumeData:仅NSURLSessionDownloadTask有此方法,用于取消时保存续传数据。Data Task 和 Upload Task 不支持。
三、NSURLSession 代理方法
1. Data Task 代理
@interface ViewController () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSMutableData *receivedData;
@end
@implementation ViewController
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
self.receivedData = [NSMutableData data];
completionHandler(NSURLSessionResponseAllow); // 必须调用,允许继续接收
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
if (error) {
NSLog(@"失败: %@", error);
} else {
NSLog(@"完成: %@", [[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding]);
}
}
@end
didReceiveResponse:completionHandler:中必须调用completionHandler(NSURLSessionResponseAllow),否则不会继续接收数据。
2. Download Task 代理(进度 + 完成)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// 下载进度
CGFloat progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = progress;
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
// location 是临时文件,必须移动到目标路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *dest = [caches stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:dest] error:nil];
}
3. Upload Task 代理(上传进度)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
CGFloat progress = (CGFloat)totalBytesSent / totalBytesExpectedToSend;
// 更新上传进度
}
四、断点续传(两种方式)
方式一:Download Task 原生断点续传(推荐)
NSURLSessionDownloadTask 原生支持断点续传,取消时生成 resumeData,恢复时用 resumeData 创建新任务:
@interface ViewController ()
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
@property (nonatomic, strong) NSData *resumeData;
@end
@implementation ViewController
- (NSURLSession *)session {
if (!_session) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
// 开始下载
- (void)startDownload {
NSURL *url = [NSURL URLWithString:@"https://example.com/large.mp4"];
self.downloadTask = [self.session downloadTaskWithURL:url];
[self.downloadTask resume];
}
// 暂停(取消并保存 resumeData)
- (void)pauseDownload {
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
self.resumeData = resumeData;
self.downloadTask = nil;
}];
}
// 恢复下载
- (void)resumeDownload {
if (self.resumeData) {
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
} else {
[self startDownload];
}
[self.downloadTask resume];
self.resumeData = nil;
}
@end
cancelByProducingResumeData:仅在服务器支持 Range 请求且已下载部分数据时才能生成 resumeData,否则回调中 resumeData 为 nil。resumeData 可持久化到磁盘,App 重启后仍可恢复。
方式二:Data Task + Range 手动实现
通过 HTTP Range 请求头手动实现断点续传,已下载部分写入文件,暂停后从文件大小位置继续:
@interface ViewController () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@property (nonatomic, strong) NSOutputStream *outputStream;
@property (nonatomic, assign) long long totalLength;
@property (nonatomic, assign) long long currentLength;
@end
@implementation ViewController
- (NSString *)filePath {
return [NSTemporaryDirectory() stringByAppendingPathComponent:@"downloadedFile"];
}
- (long long)currentFileSize {
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[self filePath] error:nil];
return [attrs[NSFileSize] longLongValue];
}
- (void)startDownload {
self.currentLength = [self currentFileSize];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/large.mp4"]];
// Range: bytes=X- 表示从第 X 字节开始下载
[request setValue:[NSString stringWithFormat:@"bytes=%lld-", self.currentLength] forHTTPHeaderField:@"Range"];
self.dataTask = [self.session dataTaskWithRequest:request];
[self.dataTask resume];
}
- (void)pauseDownload {
[self.dataTask cancel];
[self.outputStream close];
self.dataTask = nil;
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// Range 请求时 expectedContentLength 是剩余部分大小,加上已下载部分才是总大小
self.totalLength = response.expectedContentLength + self.currentLength;
self.outputStream = [NSOutputStream outputStreamToFileAtPath:[self filePath] append:YES];
[self.outputStream open];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.outputStream write:data.bytes maxLength:data.length];
self.currentLength += data.length;
CGFloat progress = (CGFloat)self.currentLength / self.totalLength;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = progress;
});
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
[self.outputStream close];
if (error) {
NSLog(@"下载失败: %@", error);
} else {
NSLog(@"下载完成: %@", [self filePath]);
}
}
@end
两种方式对比:Download Task 原生方式简单、支持后台下载、系统管理临时文件;Data Task + Range 方式灵活、可完全控制写入逻辑,但需手动管理文件和 Range。推荐使用 Download Task 原生方式。
五、AFNetworking
AFNetworking 是基于 NSURLSession 的 Objective-C 网络库,封装了请求、序列化、上传下载、网络状态检测等功能。
1. 安装
pod 'AFNetworking', '~> 4.0'
2. AFHTTPSessionManager(核心类)
AFNetworking 3.0+ 完全基于 NSURLSession,核心类是 AFHTTPSessionManager。AFHTTPRequestOperationManager(基于 NSURLConnection)在 3.0 起已被移除。
GET 请求
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://api.example.com/data"
parameters:nil
headers:nil
progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"成功: %@", responseObject); // 自动解析为 NSDictionary/NSArray
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"失败: %@", error);
}];
POST 请求(JSON)
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer]; // 请求体为 JSON
NSDictionary *params = @{@"username": @"test", @"pwd": @"123"};
[manager POST:@"https://api.example.com/login"
parameters:params
headers:nil
progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"登录成功: %@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"登录失败: %@", error);
}];
3. 文件下载
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/file.zip"]];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request
progress:^(NSProgress *downloadProgress) {
// 下载进度
NSLog(@"进度: %.2f", downloadProgress.fractionCompleted);
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// 指定文件保存路径
NSURL *caches = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [caches URLByAppendingPathComponent:response.suggestedFilename];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (!error) {
NSLog(@"下载完成: %@", filePath);
}
}];
[downloadTask resume];
4. 文件上传(multipart/form-data)
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *params = @{@"userId": @"12345"};
[manager POST:@"https://api.example.com/upload"
parameters:params
headers:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// 从文件路径上传
NSURL *fileURL = [NSURL fileURLWithPath:@"/path/to/file.jpg"];
[formData appendPartWithFileURL:fileURL name:@"file" fileName:@"photo.jpg" mimeType:@"image/jpeg" error:nil];
// 或从 NSData 上传
// [formData appendPartWithFileData:imageData name:@"file" fileName:@"photo.jpg" mimeType:@"image/jpeg"];
} progress:^(NSProgress *uploadProgress) {
NSLog(@"上传进度: %.2f", uploadProgress.fractionCompleted);
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"上传成功: %@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"上传失败: %@", error);
}];
5. 序列化器
| 序列化器 | 说明 |
|---|---|
AFJSONResponseSerializer(默认) |
响应体自动解析为 JSON(NSDictionary/NSArray) |
AFHTTPResponseSerializer |
不解析,直接返回 NSData |
AFXMLParserResponseSerializer |
响应体解析为 NSXMLParser |
AFXMLDocumentResponseSerializer(macOS) |
响应体解析为 NSXMLDocument |
AFPropertyListResponseSerializer |
响应体解析为 plist |
AFCompoundSerializer |
组合多个序列化器 |
请求序列化器:
| 序列化器 | 说明 |
|---|---|
AFHTTPRequestSerializer(默认) |
请求体为 form-urlencoded |
AFJSONRequestSerializer |
请求体为 JSON |
AFPropertyListRequestSerializer |
请求体为 plist |
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer]; // 请求 JSON
manager.responseSerializer = [AFJSONResponseSerializer serializer]; // 响应 JSON(默认)
// 接受非 JSON 响应(如 HTML 错误页)
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", nil];
6. 网络状态检测
AFNetworkReachabilityManager *reachability = [AFNetworkReachabilityManager sharedManager];
[reachability setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN: NSLog(@"蜂窝网络"); break;
case AFNetworkReachabilityStatusReachableViaWiFi: NSLog(@"WiFi"); break;
case AFNetworkReachabilityStatusNotReachable: NSLog(@"无网络"); break;
case AFNetworkReachabilityStatusUnknown: NSLog(@"未知"); break;
}
}];
[reachability startMonitoring]; // 必须调用才开始检测
六、Alamofire(Swift 对应库)
Alamofire 是 AFNetworking 的 Swift 版本,基于 NSURLSession,API 更简洁:
import Alamofire
// GET 请求
AF.request("https://api.example.com/data").responseJSON { response in
switch response.result {
case .success(let value):
print("成功: \(value)")
case .failure(let error):
print("失败: \(error)")
}
}
// POST 请求(JSON)
let params = ["username": "test", "pwd": "123"]
AF.request("https://api.example.com/login", method: .post, parameters: params, encoding: JSONEncoding.default)
.responseDecodable(of: UserResponse.self) { response in
// 自动解码为 Codable 模型
}
// 下载
let destination: DownloadRequest.Destination = { _, _ in
let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let fileURL = caches.appendingPathComponent("file.zip")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download("https://example.com/file.zip", to: destination)
.downloadProgress { progress in
print("进度: \(progress.fractionCompleted)")
}
.response { response in
print("下载完成: \(response.fileURL)")
}
// 上传
AF.upload(multipartFormData: { formData in
formData.append(fileURL, withName: "file", fileName: "photo.jpg", mimeType: "image/jpeg")
}, to: "https://api.example.com/upload")
.uploadProgress { progress in
print("上传进度: \(progress.fractionCompleted)")
}
.responseJSON { response in
print(response)
}
// 网络状态
let reachability = NetworkReachabilityManager()
reachability?.startListening { status in
switch status {
case .reachable(.cellular): print("蜂窝")
case .reachable(.ethernetOrWiFi): print("WiFi")
case .notReachable: print("无网络")
case .unknown: print("未知")
}
}
七、三种方式对比
| 对比项 | NSURLConnection | NSURLSession | AFNetworking |
|---|---|---|---|
| 状态 | iOS 9 起废弃 | 苹果推荐 | 第三方主流 |
| 底层 | 旧网络栈 | 现代网络栈 | 基于 NSURLSession |
| 任务管理 | 无 | Data/Download/Upload Task | 封装 Task |
| 后台下载 | 不支持 | 支持 | 支持(封装) |
| 断点续传 | 手动 Range | 原生 resumeData | 封装 |
| 序列化 | 手动 | 手动 | 自动(JSON/XML/HTTP) |
| 上传 | 手动构建 body | Upload Task | multipart 一行代码 |
| 网络检测 | 无 | 无 | 内置 Reachability |
| 代码量 | 多 | 中 | 少 |
| 适用场景 | 维护老项目 | 所有项目(推荐) | 快速开发 |
八、总结
- NSURLConnection:iOS 早期网络 API,iOS 9 起废弃,基于 RunLoop 调度回调,子线程需手动处理 RunLoop,新项目不使用。
- NSURLSession:iOS 7+ 推荐 API,三种任务类型(DataTask 普通请求/DownloadTask 文件下载/UploadTask 文件上传),支持后台下载、原生断点续传(cancelByProducingResumeData + downloadTaskWithResumeData)、配置灵活(default/ephemeral/background)。任务默认挂起,必须 resume;completionHandler 在子线程执行,UI 更新需回主线程。
- 断点续传两种方式:① DownloadTask 原生 resumeData(推荐,简单,支持后台);② DataTask + Range 请求头手动实现(灵活,需管理文件和 Range)。
- AFNetworking:基于 NSURLSession 的 OC 第三方库,3.0+ 移除了基于 NSURLConnection 的 AFHTTPRequestOperationManager,核心类 AFHTTPSessionManager;自动 JSON 序列化、multipart 上传一行代码、内置网络状态检测;Swift 对应库为 Alamofire。
- 序列化器:请求序列化(HTTP form/JSON/plist)、响应序列化(JSON/HTTP/XML/plist/Compound),默认 JSON。
- 选择建议:新项目用 NSURLSession 或 AFNetworking/Alamofire;需要简单封装和序列化用 AFNetworking;需要完全控制和后台下载用 NSURLSession;老项目维护才接触 NSURLConnection。

浙公网安备 33010602011771号