Objective-C网络编程基础与NSURLSession实战
1. Objective - C网络编程基础
1.1 网络编程概述
在当今移动应用和互联网高度发展的时代,网络编程是开发中不可或缺的一部分。Objective - C作为苹果开发生态中重要的编程语言,其网络编程能力至关重要。网络编程在Objective - C中主要涉及与服务器进行数据交互,获取数据、上传数据等操作,以便为用户提供丰富的在线功能,比如加载图片、获取新闻资讯、上传用户信息等。
1.2 URL基础知识
URL(Uniform Resource Locator),统一资源定位符,是网络编程中用于定位资源的关键概念。在Objective - C网络编程里,我们通过URL来指定要访问的服务器地址以及资源路径。一个完整的URL通常包含协议(如http、https)、主机名(如www.example.com)、端口号(默认http为80,https为443,可省略)、路径(如 /index.html)和查询参数(如?param1 = value1¶m2 = value2)等部分。例如:https://www.apple.com/news/?category = technology
,其中https
是协议,www.apple.com
是主机名,/news/
是路径,?category = technology
是查询参数。
1.3 HTTP协议
HTTP(Hyper - Text Transfer Protocol),超文本传输协议,是Objective - C网络编程中最常用的协议。它基于请求 - 响应模型。客户端(如iOS应用)向服务器发送HTTP请求,服务器接收到请求后进行处理,并返回HTTP响应。
- HTTP请求:包含请求行(如
GET /index.html HTTP/1.1
,其中GET
是请求方法,/index.html
是请求资源路径,HTTP/1.1
是协议版本)、请求头(如Content - Type: application/json
用于指定发送的数据类型)和请求体(对于GET
请求,请求体通常为空;POST
请求可在请求体中发送数据)。常见的HTTP请求方法有GET
、POST
、PUT
、DELETE
等。GET
方法通常用于获取资源,数据会附加在URL的查询参数中;POST
方法用于提交数据,数据放在请求体中,相对更安全且可传输大量数据。 - HTTP响应:由状态行(如
HTTP/1.1 200 OK
,200
是状态码,表示请求成功,OK
是状态描述)、响应头(如Content - Length: 1024
表示响应数据的长度)和响应体(包含请求资源的数据,如HTML页面内容、JSON数据等)组成。常见的HTTP状态码有200(成功)、404(未找到资源)、500(服务器内部错误)等。
2. NSURLSession简介
2.1 NSURLSession是什么
NSURLSession是iOS 7.0引入的用于网络请求的类,它替代了之前的NSURLConnection。NSURLSession提供了更灵活、高效且易于使用的网络编程接口,支持数据任务(用于获取数据)、上传任务(用于上传文件等数据)和下载任务(用于下载文件)。它基于任务(NSURLSessionTask)的概念,每个网络请求都可以看作是一个任务,方便管理和控制。
2.2 NSURLSession的优势
- 高效的网络请求:NSURLSession在网络请求方面进行了优化,能够更有效地利用网络资源,提高请求速度。例如,它支持HTTP/2协议,相比HTTP/1.1在性能上有显著提升,如多路复用、头部压缩等特性可以减少请求的延迟。
- 灵活的任务管理:可以轻松创建、暂停、恢复和取消任务。比如在一个应用中有多个图片下载任务,使用NSURLSession可以方便地对每个下载任务进行单独控制,当用户切换页面时,可以暂停不必要的下载任务,节省网络流量和系统资源。
- 后台任务支持:支持在后台执行网络任务,即使应用进入后台,任务依然可以继续执行。这对于一些需要长时间运行的网络操作,如下载大文件或者上传大量数据非常有用,不会因为应用进入后台而中断任务。
2.3 NSURLSession的组成部分
- NSURLSessionConfiguration:用于配置NSURLSession的行为。它有三种类型:
defaultSessionConfiguration
(默认配置,适用于大多数应用场景,使用应用的缓存策略和证书验证等)、ephemeralSessionConfiguration
(临时配置,不会持久化任何数据,包括缓存、证书等,适用于对隐私要求较高的场景)和backgroundSessionConfigurationWithIdentifier:
(后台配置,用于创建可以在后台运行的NSURLSession)。例如,我们可以通过以下方式创建一个带有自定义超时时间的默认配置:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 15; // 设置请求超时时间为15秒
- NSURLSession:根据配置创建的会话对象,负责管理网络请求任务。可以通过
NSURLSession
的类方法创建不同类型的会话,如+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
。 - NSURLSessionTask:代表一个网络请求任务,是NSURLSession实际执行网络操作的单元。它有三个子类:
NSURLSessionDataTask
(用于获取数据,如获取JSON数据、XML数据等)、NSURLSessionUploadTask
(用于上传数据,如上传图片、文件等)和NSURLSessionDownloadTask
(用于下载文件)。
3. NSURLSession实战 - 数据任务
3.1 创建数据任务
数据任务是最常用的NSURLSession任务之一,用于从服务器获取数据。创建数据任务的步骤如下:
- 创建NSURL对象,指定请求的URL。
- 创建NSMutableURLRequest对象(或使用NSURLRequest,若请求无需动态修改),设置请求方法、请求头和请求体等。
- 通过NSURLSession的
dataTaskWithRequest:
方法创建数据任务。
以下是一个简单的示例,通过GET请求获取一个JSON数据:
NSURL *url = [NSURL URLWithString:@"https://api.example.com/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"GET";
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"请求错误: %@", error);
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSError *jsonError;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if (!jsonError) {
NSLog(@"获取到的JSON数据: %@", jsonObject);
} else {
NSLog(@"解析JSON数据错误: %@", jsonError);
}
} else {
NSLog(@"HTTP状态码错误: %ld", (long)httpResponse.statusCode);
}
}
}
}];
[dataTask resume];
在上述代码中,首先创建了请求的URL和请求对象,设置请求方法为GET
。然后通过[NSURLSession sharedSession]
获取一个共享的NSURLSession实例(共享会话适用于大多数应用场景,它会复用网络连接等资源),并使用该会话创建数据任务。数据任务的completionHandler
是在任务完成时回调的块,其中data
是服务器返回的数据,response
是服务器的响应,error
如果请求过程中发生错误则会包含错误信息。我们在回调中对错误进行处理,并解析返回的JSON数据。
3.2 处理POST请求
POST请求通常用于向服务器提交数据,例如用户登录信息、评论内容等。与GET请求不同,POST请求的数据放在请求体中。以下是一个POST请求示例,向服务器提交用户登录信息:
NSURL *url = [NSURL URLWithString:@"https://api.example.com/login"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.allHTTPHeaderFields = @{@"Content - Type": @"application/json"};
NSDictionary *parameters = @{@"username": @"user123", @"password": @"password123"};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:NSJSONWritingPrettyPrinted error:&error];
if (error) {
NSLog(@"创建JSON数据错误: %@", error);
return;
}
request.HTTPBody = jsonData;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"请求错误: %@", error);
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"登录响应: %@", responseString);
} else {
NSLog(@"HTTP状态码错误: %ld", (long)httpResponse.statusCode);
}
}
}
}];
[dataTask resume];
在这个示例中,首先设置请求方法为POST
,并设置Content - Type
为application/json
,表示提交的数据是JSON格式。然后将用户登录信息(用户名和密码)转换为JSON数据,并设置为请求体。最后创建数据任务并处理响应,这里假设服务器返回的是一个简单的字符串表示登录结果。
4. NSURLSession实战 - 上传任务
4.1 上传文件
上传任务可以用于上传文件,如图片、文档等。以下是一个上传图片的示例:
NSURL *url = [NSURL URLWithString:@"https://api.example.com/upload"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
NSString *boundary = @"Boundary - string - 1234567890";
NSString *contentType = [NSString stringWithFormat:@"multipart/form - data; boundary=%@", boundary];
request.allHTTPHeaderFields = @{@"Content - Type": contentType};
UIImage *image = [UIImage imageNamed:@"example.jpg"];
NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
NSMutableData *body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content - Disposition: form - data; name=\"file\"; filename=\"example.jpg\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content - Type: image/jpeg\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:imageData];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = body;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"上传错误: %@", error);
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"上传成功响应: %@", responseString);
} else {
NSLog(@"HTTP状态码错误: %ld", (long)httpResponse.statusCode);
}
}
}
}];
[uploadTask resume];
在这个示例中,我们创建了一个multipart/form - data
格式的请求,这种格式常用于上传文件。首先设置请求头,包括Content - Type
,并生成一个边界字符串(boundary
)用于分隔请求体中的不同部分。然后将图片数据转换为NSData
,并按照multipart/form - data
的格式组装请求体,包括文件名、文件类型等信息。最后创建上传任务并处理响应。
4.2 上传进度监控
有时我们需要监控上传的进度,以便给用户提供反馈。NSURLSessionUploadTask提供了一个代理方法来实现这一功能。首先,我们需要设置NSURLSession的代理:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
这里将当前类设置为代理,并需要实现NSURLSessionTaskDelegate
协议中的URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
方法:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
float progress = (float)totalBytesSent / (float)totalBytesExpectedToSend;
NSLog(@"上传进度: %.2f%%", progress * 100);
}
在这个方法中,通过已发送的字节数和总字节数计算上传进度,并打印出来。这样就可以实时监控上传的进度情况。
5. NSURLSession实战 - 下载任务
5.1 简单文件下载
下载任务用于从服务器下载文件,如图片、视频、文档等。以下是一个简单的文件下载示例:
NSURL *url = [NSURL URLWithString:@"https://example.com/download/file.zip"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"下载错误: %@", error);
} else {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectoryURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *destinationURL = [documentsDirectoryURL URLByAppendingPathComponent:response.suggestedFilename];
NSError *moveError;
[fileManager moveItemAtURL:location toURL:destinationURL error:&moveError];
if (moveError) {
NSLog(@"移动文件错误: %@", moveError);
} else {
NSLog(@"文件下载成功,保存路径: %@", destinationURL);
}
}
}];
[downloadTask resume];
在这个示例中,首先创建下载请求的URL和请求对象。然后通过共享会话创建下载任务。下载任务完成后,completionHandler
中的location
是下载文件在临时目录的URL,我们需要将其移动到应用的文档目录(这里通过NSFileManager
获取文档目录URL,并使用response.suggestedFilename
获取服务器建议的文件名)。如果移动文件成功,则表示下载成功并打印保存路径。
5.2 下载进度监控
与上传任务类似,下载任务也可以监控进度。同样需要设置NSURLSession的代理,并实现NSURLSessionDownloadDelegate
协议中的URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
方法:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
float progress = (float)totalBytesWritten / (float)totalBytesExpectedToWrite;
NSLog(@"下载进度: %.2f%%", progress * 100);
}
在这个方法中,通过已写入的字节数和总字节数计算下载进度,并打印出来,从而实现对下载进度的实时监控。
6. 处理HTTPS请求
6.1 信任服务器证书
在Objective - C网络编程中,处理HTTPS请求时,需要确保与服务器之间的连接是安全的。这涉及到服务器证书的验证。默认情况下,NSURLSession会自动验证服务器的证书是否有效。但在某些情况下,如开发环境中使用自签名证书时,需要手动处理证书信任。
首先,设置NSURLSession的代理,并实现NSURLSessionDelegate
协议中的URLSession:didReceiveChallenge:completionHandler:
方法:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
}
在这个方法中,当接收到证书验证挑战时,首先判断挑战的认证方法是否为NSURLAuthenticationMethodServerTrust
,如果是,则创建一个基于服务器信任的证书凭证,并使用NSURLSessionAuthChallengeUseCredential
disposition来告诉系统使用该凭证继续请求。
6.2 自定义证书验证
除了简单地信任服务器证书,还可以进行更严格的自定义证书验证。例如,验证证书的颁发机构、证书的有效期等。以下是一个简单的自定义证书验证示例:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustSetPolicy(serverTrust, policy);
CFRelease(policy);
NSArray *certificates = challenge.protectionSpace.serverTrust.certificates;
NSData *serverCertData = CFBridgingRelease(SecCertificateCopyData((__bridge SecCertificateRef)(certificates[0])));
// 这里可以添加更多自定义验证逻辑,如验证证书有效期等
BOOL isValid = [self validateServerCertificate:serverCertData];
if (isValid) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
}
- (BOOL)validateServerCertificate:(NSData *)serverCertData {
// 简单示例,这里可以实现更复杂的验证逻辑
// 例如,验证证书的颁发机构是否为可信机构
return YES;
}
在这个示例中,首先创建一个基本的X509证书策略,并设置到服务器信任对象上。然后获取服务器证书的数据,并在validateServerCertificate:
方法中进行自定义验证逻辑。如果验证通过,则创建证书凭证并继续请求;否则,取消认证挑战。
7. 网络缓存策略
7.1 NSURLRequest的缓存策略
NSURLRequest提供了多种缓存策略,可以通过设置cachePolicy
属性来指定。常见的缓存策略有:
- NSURLRequestUseProtocolCachePolicy:使用协议默认的缓存策略。对于HTTP协议,它会根据服务器返回的
Cache - Control
和Expires
头信息来决定是否使用缓存。 - NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存数据,每次都从服务器重新获取数据。适用于数据更新频繁且需要实时获取最新数据的场景,如股票行情数据。
- NSURLRequestReturnCacheDataElseLoad:先尝试从本地缓存获取数据,如果缓存不存在,则从服务器加载。适用于对数据实时性要求不高的场景,如一些不经常更新的新闻资讯。
- NSURLRequestReturnCacheDataDontLoad:只从本地缓存获取数据,如果缓存不存在,则不进行网络请求,直接返回错误。适用于应用在离线状态下仍希望显示一些缓存数据的场景。
以下是设置缓存策略的示例:
NSURL *url = [NSURL URLWithString:@"https://api.example.com/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
在这个示例中,设置请求的缓存策略为NSURLRequestReturnCacheDataElseLoad
,即先尝试从本地缓存获取数据,如果没有则从服务器加载。
7.2 NSURLCache的使用
NSURLCache是管理网络缓存的类。可以通过它来设置全局的缓存策略、缓存大小等。例如,设置全局的缓存大小为10MB:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:10 * 1024 * 1024 diskCapacity:10 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
这里创建了一个内存缓存和磁盘缓存都为10MB的NSURLCache
实例,并设置为全局共享缓存。这样,所有使用默认配置的NSURLSession请求都会受到这个缓存设置的影响。同时,也可以通过NSURLCache
的方法来清除缓存数据,如removeAllCachedResponses
方法可以清除所有缓存响应:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
这在某些情况下非常有用,比如当用户手动刷新数据或者应用更新后,希望清除旧的缓存数据以获取最新内容。
8. 错误处理
8.1 常见网络错误类型
在网络编程中,会遇到各种错误。常见的网络错误类型有:
- 网络连接错误:如网络不可用、WiFi未连接、蜂窝数据未开启等。这种情况下,
NSError
的domain
通常为NSURLErrorDomain
,code
可能为NSURLErrorNotConnectedToInternet
(表示未连接到互联网)。 - 请求超时错误:当请求在规定的时间内没有得到服务器响应时,会发生请求超时错误。
NSError
的domain
为NSURLErrorDomain
,code
可能为NSURLErrorTimedOut
。 - 服务器错误:服务器返回的HTTP状态码表示服务器内部错误,如500(服务器内部错误)、502(错误网关)等。这种情况下,虽然请求成功发送到服务器,但服务器处理过程中出现问题。
- 解析错误:当服务器返回的数据格式不符合预期,导致解析失败时,会发生解析错误。例如,期望返回JSON数据,但实际返回的数据格式不正确,无法使用
NSJSONSerialization
进行解析。
8.2 错误处理策略
在Objective - C网络编程中,处理错误非常重要。对于不同类型的错误,应采取不同的处理策略:
- 网络连接错误:可以提示用户检查网络连接,如显示一个提示框告知用户“请检查网络连接”,并提供跳转到系统网络设置页面的按钮。
- 请求超时错误:可以提示用户请求超时,建议用户重试。同时,可以在代码中实现自动重试机制,例如设置一个重试次数,当发生超时错误时,自动重新发起请求,直到达到最大重试次数或者请求成功。
- 服务器错误:可以根据具体的HTTP状态码给用户提供相应的提示。如500错误可以提示“服务器出现问题,请稍后重试”,502错误可以提示“服务器网关错误,请稍后重试”。
- 解析错误:可以提示用户服务器返回的数据格式不正确,同时记录错误日志,以便开发人员进行调试。例如,使用
NSLog
记录解析错误信息,包括错误描述和可能导致错误的数据片段。
以下是一个在数据任务中处理错误的示例:
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
if (error.domain == NSURLErrorDomain) {
switch (error.code) {
case NSURLErrorNotConnectedToInternet:
NSLog(@"网络未连接,请检查网络设置");
break;
case NSURLErrorTimedOut:
NSLog(@"请求超时,是否重试?");
// 这里可以添加自动重试逻辑
break;
default:
NSLog(@"其他网络错误: %@", error);
break;
}
} else {
NSLog(@"其他错误: %@", error);
}
} else {
// 处理成功响应
}
}];
[dataTask resume];
在这个示例中,当发生错误时,首先判断错误的domain
是否为NSURLErrorDomain
,如果是,则根据不同的code
进行相应的处理和提示。这样可以更友好地向用户反馈错误信息,并根据错误类型采取合适的应对措施。