iOS开发基础44-网络编程之NSURLSession&AFN
在iOS开发中,网络请求是一个非常常见的需求。本文将详细介绍三种网络请求的方式:NSURLConnection
、NSURLSession
、以及第三方库AFNetworking
,并对相关知识点进行补充和分析。
一、NSURLConnection(补充)
NSURLConnection
是一种较早期的iOS网络请求工具,但它仍然有很大一部分应用在使用。它可以通过同步和异步方式来进行网络请求。
NSURLConnection的使用
1. 初始化NSURLConnection请求
使用NSURLConnection
进行异步请求需要实现相关的Delegate方法:
@interface MyViewController () <NSURLConnectionDataDelegate>
@property (nonatomic, strong) NSMutableData *receivedData;
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
2. NSURLConnectionDataDelegate方法
在实现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 {
// Handle received data
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"Connection failed: %@", error.localizedDescription);
}
3. 线程处理
默认情况下,NSURLConnection
的回调方法是在主线程中执行的。如果在子线程中发送请求,需要手动将NSURLConnection
加入到RunLoop
:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com"]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
[[NSRunLoop currentRunLoop] run];
});
二、NSURLSession
NSURLSession
是NSURLConnection
的升级版,提供了更加现代、灵活的API,支持后台下载、任务恢复、数据保护等功能。
NSURLSession的使用
1. 创建NSURLSession
有多种方式创建NSURLSession
,可以通过共享session、默认session或自定义session:
NSURLSession *session = [NSURLSession sharedSession];
或者创建带有配置的自定义session:
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
2. 创建任务并执行
可以创建数据任务、下载任务或上传任务:
NSURL *url = [NSURL URLWithString:@"http://example.com"];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
// Handle data
}
}];
[dataTask resume];
NSURLSessionTask常用方法
[task suspend]; // 暂停任务
[task resume]; // 继续任务
[task cancel]; // 取消任务
取消任务时可以保存进度:
[task cancelByProducingResumeData:^(NSData *resumeData) {
// Save resumeData
}];
代理方法
NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
completionHandler(NSURLSessionResponseAllow); // 允许接收数据
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Handle received data
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
NSLog(@"Error: %@", error);
} else {
// Task completed successfully
}
}
NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// Handle download progress
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// Handle file download completion
}
NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
// Handle upload progress
}
断点下载
断点下载在需要暂停和恢复任务时非常有用。以下是一个简单的断点下载实现:
#import "ViewController.h"
@interface ViewController () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@property (nonatomic, strong) NSOutputStream *outputStream;
@property (nonatomic, assign) NSUInteger totalLength;
@property (nonatomic, assign) NSUInteger currentLength;
@property (nonatomic, strong) NSData *resumeData;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.currentLength = [self getFileSizeAtPath:[self filePath]];
}
- (IBAction)startDownload:(id)sender {
[self.dataTask resume];
}
- (IBAction)pauseDownload:(id)sender {
[self.dataTask suspend];
}
- (IBAction)resumeDownload:(id)sender {
if (self.resumeData) {
self.dataTask = [self.session downloadTaskWithResumeData:self.resumeData];
}
[self.dataTask resume];
}
- (NSUInteger)getFileSizeAtPath:(NSString *)path {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
return [attributes[NSFileSize] unsignedIntegerValue];
}
return 0;
}
- (NSString *)filePath {
return [NSTemporaryDirectory() stringByAppendingPathComponent:@"downloadedFile"];
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
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;
self.progressView.progress = 1.0 * self.currentLength / self.totalLength;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (!error) {
NSLog(@"Download completed.");
} else {
NSLog(@"Download failed: %@", error.localizedDescription);
}
[self.outputStream close];
}
#pragma mark - Lazy Loading
- (NSURLSession *)session {
if (!_session) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
- (NSURLSessionDataTask *)dataTask {
if (!_dataTask) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/path/to/file"]];
NSString *range = [NSString stringWithFormat:@"bytes=%zd-", self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
_dataTask = [self.session dataTaskWithRequest:request];
}
return _dataTask;
}
@end
三、AFNetworking
AFNetworking
是一个基于NSURLSession
的流行iOS网络请求库,提供了更简洁高效的API,用于处理网络请求、文件下载、上传和序列化等操作。
AFNetworking的使用
1. 安装AFNetworking
推荐使用CocoaPods来安装AFNetworking
:
pod 'AFNetworking', '~> 4.0'
然后运行pod install
。
2. 使用AFHTTPRequestOperationManager进行请求
GET请求
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"http://example.com/api" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
POST请求
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
[manager POST:@"http://example.com/api" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
3. 使用AFHTTPSessionManager进行请求
GET请求
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"http://example.com/api" parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"Error: %@", error);
}];
POST请求
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
[manager POST:@"http://example.com/api" parameters:parameters headers:nil progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"Error: %@", error);
}];
4. 文件下载
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/file.zip"]];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (!error) {
NSLog(@"File downloaded to: %@", filePath);
} else {
NSLog(@"Error: %@", error);
}
}];
[downloadTask resume];
5. 文件上传
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
[manager POST:@"http://example.com/upload" parameters:parameters headers:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSURL *fileURL = [NSURL fileURLWithPath:@"filePath"];
[formData appendPartWithFileURL:fileURL name:@"file" error:nil];
} progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"Success: %@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"Error: %@", error);
}];
6. 序列化
AFNetworking默认支持JSON数据的自动序列化和反序列化,但也提供了灵活的自定义方法:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
// or
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
7. 网络状态检测
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"Cellular");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WiFi");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"No Connection");
break;
default:
break;
}
}];
[manager startMonitoring];
六、总结
在iOS网络请求中,NSURLConnection
、NSURLSession
和AFNetworking
各有特点:
NSURLConnection
是最基础的方式,但需要手动管理较多的细节。NSURLSession
提供了更多现代化特性和更好的性能。AFNetworking
基于NSURLSession
,提供了更简洁的API,简化了常见操作。
选择哪种方式取决于具体的项目需求和开发者的习惯。