Objective-C网络请求的重试与超时处理策略
网络请求的重要性与挑战
在移动应用开发中,网络请求是至关重要的一部分。无论是获取数据、上传用户信息还是与服务器进行实时交互,都离不开网络请求。然而,网络环境的复杂性给开发者带来了诸多挑战。网络可能不稳定、信号弱甚至中断,服务器也可能出现过载、维护等情况。这就需要我们在进行网络请求时,采取有效的重试与超时处理策略,以确保应用的稳定性和用户体验。
Objective-C 网络请求基础
在 Objective-C 中,我们通常使用 NSURLSession
来进行网络请求。NSURLSession
是 iOS 7.0 引入的用于处理 URL 加载任务的 API,它提供了更加灵活和强大的功能。
以下是一个简单的 NSURLSession
发送 GET 请求的示例:
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
NSLog(@"Error: %@", error);
}
}];
[task resume];
在上述代码中,我们创建了一个 NSURL
对象表示请求的 URL,然后通过 NSURLSession
的 dataTaskWithURL:completionHandler:
方法创建一个数据任务,并在完成处理程序中处理响应数据或错误。
超时处理策略
设置超时时间
NSURLSession
提供了设置超时时间的方法。对于 NSURLSessionDataTask
,我们可以通过 NSURLRequest
的 timeoutInterval
属性来设置请求的超时时间。
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置超时时间为 10 秒
request.timeoutInterval = 10;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
NSLog(@"Error: %@", error);
}
}];
[task resume];
在上述代码中,我们通过 request.timeoutInterval = 10;
设置了请求的超时时间为 10 秒。如果在 10 秒内请求没有完成,将会触发超时错误。
处理超时错误
当请求超时时,completionHandler
中的 error
参数会包含相应的错误信息。我们可以通过检查错误的 domain
和 code
来判断是否是超时错误。
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.timeoutInterval = 10;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorTimedOut) {
NSLog(@"请求超时,可考虑重试");
} else {
NSLog(@"其他错误: %@", error);
}
} else if (data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
}
}];
[task resume];
在上述代码中,我们通过 [error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorTimedOut
来判断是否是超时错误,并在超时时打印提示信息。
重试处理策略
简单重试
简单重试是指在请求失败后,立即重新发起请求。我们可以通过递归的方式来实现简单重试。
@interface NetworkManager : NSObject
- (void)sendRequestWithRetryCount:(NSInteger)retryCount;
@end
@implementation NetworkManager
- (void)sendRequestWithRetryCount:(NSInteger)retryCount {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
if (retryCount > 0) {
NSLog(@"请求失败,重试次数: %ld", (long)retryCount);
[self sendRequestWithRetryCount:retryCount - 1];
} else {
NSLog(@"重试次数用尽,请求失败: %@", error);
}
}
}];
[task resume];
}
@end
使用时:
NetworkManager *manager = [[NetworkManager alloc] init];
[manager sendRequestWithRetryCount:3];
在上述代码中,NetworkManager
类的 sendRequestWithRetryCount:
方法通过递归调用自身来实现重试。每次请求失败且重试次数大于 0 时,会重新发起请求并减少重试次数。当重试次数用尽时,打印请求失败信息。
指数退避重试
指数退避重试是一种更智能的重试策略。它在每次重试时,增加重试的间隔时间,以避免在短时间内频繁重试对服务器造成过大压力。间隔时间通常按照指数增长。
@interface NetworkManager : NSObject
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay;
@end
@implementation NetworkManager
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
if (retryCount > 0) {
NSTimeInterval delay = initialDelay * pow(2, (double)(3 - retryCount));
NSLog(@"请求失败,重试次数: %ld,延迟 %.2f 秒后重试", (long)retryCount, delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self sendRequestWithRetryCount:retryCount - 1 initialDelay:initialDelay];
});
} else {
NSLog(@"重试次数用尽,请求失败: %@", error);
}
}
}];
[task resume];
}
@end
使用时:
NetworkManager *manager = [[NetworkManager alloc] init];
[manager sendRequestWithRetryCount:3 initialDelay:1];
在上述代码中,sendRequestWithRetryCount:initialDelay:
方法根据重试次数计算出每次重试的延迟时间 delay
,然后使用 dispatch_after
在延迟时间后重新发起请求。初始延迟时间 initialDelay
可以根据实际情况调整,这里设置为 1 秒。每次重试的延迟时间按照指数增长,如第一次重试延迟 1 秒,第二次重试延迟 2 秒,第三次重试延迟 4 秒。
结合超时与重试
在实际应用中,我们通常需要将超时处理与重试策略结合起来。
@interface NetworkManager : NSObject
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay;
@end
@implementation NetworkManager
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.timeoutInterval = 10;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
if (retryCount > 0) {
if ([error.domain isEqualToString:NSURLErrorDomain] && (error.code == NSURLErrorTimedOut || error.code == NSURLErrorCannotConnectToHost)) {
NSTimeInterval delay = initialDelay * pow(2, (double)(3 - retryCount));
NSLog(@"请求失败(超时或连接失败),重试次数: %ld,延迟 %.2f 秒后重试", (long)retryCount, delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self sendRequestWithRetryCount:retryCount - 1 initialDelay:initialDelay];
});
} else {
NSLog(@"其他错误,不再重试: %@", error);
}
} else {
NSLog(@"重试次数用尽,请求失败: %@", error);
}
}
}];
[task resume];
}
@end
使用时:
NetworkManager *manager = [[NetworkManager alloc] init];
[manager sendRequestWithRetryCount:3 initialDelay:1];
在上述代码中,我们在 completionHandler
中首先判断是否有错误。如果有错误且重试次数大于 0,进一步判断错误是否是超时错误(NSURLErrorTimedOut
)或无法连接到主机错误(NSURLErrorCannotConnectToHost
)。如果是这两种错误之一,执行指数退避重试;否则,打印其他错误信息并停止重试。
实际应用中的考虑因素
网络类型检测
在进行网络请求重试和超时处理时,了解当前的网络类型是很有帮助的。例如,在蜂窝网络下,可能需要更保守的重试策略,以避免消耗过多用户流量。我们可以使用 Reachability
类来检测网络状态。
#import "Reachability.h"
// 获取网络状态
Reachability *reachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [reachability currentReachabilityStatus];
if (networkStatus == ReachableViaWiFi) {
// WiFi 网络,可采用相对激进的重试策略
} else if (networkStatus == ReachableViaWWAN) {
// 蜂窝网络,采用更保守的重试策略
} else {
// 无网络连接
}
用户体验优化
在进行重试时,要注意用户体验。频繁的重试可能会让用户感到烦躁,尤其是在没有任何提示的情况下。我们可以在界面上显示加载指示器,并在重试时更新指示器的状态,告知用户正在进行重试操作。
// 显示加载指示器
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityIndicator startAnimating];
[self.view addSubview:activityIndicator];
// 重试逻辑
// 在每次重试前更新加载指示器状态,如显示重试次数等信息
服务器端配合
在某些情况下,服务器端也可以对重试和超时进行优化。例如,服务器可以返回特定的 HTTP 状态码(如 503 Service Unavailable)来告知客户端需要重试,并可以在响应头中提供建议的重试时间间隔。客户端可以根据这些信息来调整重试策略。
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 503) {
// 服务器告知需要重试
NSString *retryAfter = [httpResponse.allHeaderFields objectForKey:@"Retry-After"];
if (retryAfter) {
NSTimeInterval delay = [retryAfter doubleValue];
// 根据服务器建议的时间间隔进行重试
}
}
// 处理响应数据
} else {
// 处理错误
}
}];
[task resume];
总结常见问题与解决办法
- 重试次数过多导致应用卡顿:
- 原因:指数退避重试中,如果初始延迟时间设置过小,或者重试次数过多,可能会导致在短时间内频繁发起请求,占用大量系统资源,使应用卡顿。
- 解决办法:合理设置初始延迟时间和最大重试次数。可以根据网络类型动态调整,如在 WiFi 网络下适当增加重试次数和减小初始延迟,在蜂窝网络下反之。同时,可以使用队列来管理请求,避免同时发起过多请求。
- 超时时间设置不合理:
- 原因:超时时间设置过短,可能导致正常请求也被判定为超时;设置过长,则可能长时间等待无响应的请求,影响用户体验。
- 解决办法:根据请求的性质和网络环境来设置超时时间。对于简单的数据获取请求,可以设置相对较短的超时时间;对于复杂的上传或下载任务,适当延长超时时间。同时,可以通过统计分析实际网络请求的响应时间,来优化超时时间的设置。
- 重试时数据一致性问题:
- 原因:在重试过程中,如果服务器端数据已经发生变化,可能会导致多次重试获取到的数据不一致。
- 解决办法:可以在请求中添加唯一标识符(如 UUID),服务器端根据标识符来处理请求,确保多次重试时数据的一致性。或者在客户端对重试获取的数据进行校验,如通过数据版本号等方式判断数据是否为最新。
- 网络切换时重试策略混乱:
- 原因:当网络在 WiFi 和蜂窝网络之间切换时,原有的重试策略可能不再适用。
- 解决办法:结合
Reachability
类监听网络状态变化,当网络类型发生改变时,根据新的网络类型调整重试策略。例如,从 WiFi 切换到蜂窝网络时,适当减少重试次数或增大初始延迟时间。
高级应用场景与优化技巧
- 并发请求的重试与超时:
- 在实际应用中,可能会同时发起多个网络请求。对于并发请求,我们需要分别管理每个请求的重试与超时。可以使用
NSOperationQueue
来管理这些请求任务。 - 示例代码:
- 在实际应用中,可能会同时发起多个网络请求。对于并发请求,我们需要分别管理每个请求的重试与超时。可以使用
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 3; // 设置最大并发数
NSArray *urls = @[
[NSURL URLWithString:@"https://example.com/api/data1"],
[NSURL URLWithString:@"https://example.com/api/data2"],
[NSURL URLWithString:@"https://example.com/api/data3"]
];
for (NSURL *url in urls) {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response for %@: %@", url, responseString);
} else {
// 处理错误,可根据需求进行重试
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorTimedOut) {
NSLog(@"请求 %@ 超时,可考虑重试", url);
} else {
NSLog(@"请求 %@ 其他错误: %@", url, error);
}
}
}];
[task resume];
}];
[queue addOperation:operation];
}
- 在上述代码中,我们创建了一个 `NSOperationQueue` 并设置了最大并发数为 3。然后为每个 URL 创建一个 `NSBlockOperation`,在操作的 block 中发起网络请求。这样可以方便地管理并发请求的超时和重试逻辑。
2. 重试与缓存结合: - 在某些情况下,我们可以结合缓存来优化重试策略。如果重试请求与之前成功的请求相同,且缓存数据未过期,可以直接使用缓存数据,避免不必要的网络请求。 - 示例代码:
@interface NetworkManager : NSObject
@property (nonatomic, strong) NSCache *cache;
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay;
@end
@implementation NetworkManager
- (instancetype)init {
self = [super init];
if (self) {
self.cache = [[NSCache alloc] init];
}
return self;
}
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSString *cacheKey = url.absoluteString;
id cachedData = [self.cache objectForKey:cacheKey];
if (cachedData) {
// 使用缓存数据
NSLog(@"Using cached data for %@", url);
return;
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
[self.cache setObject:data forKey:cacheKey]; // 缓存数据
} else {
if (retryCount > 0) {
if ([error.domain isEqualToString:NSURLErrorDomain] && (error.code == NSURLErrorTimedOut || error.code == NSURLErrorCannotConnectToHost)) {
NSTimeInterval delay = initialDelay * pow(2, (double)(3 - retryCount));
NSLog(@"请求失败(超时或连接失败),重试次数: %ld,延迟 %.2f 秒后重试", (long)retryCount, delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self sendRequestWithRetryCount:retryCount - 1 initialDelay:initialDelay];
});
} else {
NSLog(@"其他错误,不再重试: %@", error);
}
} else {
NSLog(@"重试次数用尽,请求失败: %@", error);
}
}
}];
[task resume];
}
@end
- 在上述代码中,我们在 `NetworkManager` 类中添加了一个 `NSCache` 属性来缓存请求数据。在每次发起请求前,先检查缓存中是否有对应的数据,如果有则直接使用,避免了不必要的网络请求和重试。
3. 自适应重试策略: - 自适应重试策略根据网络环境和服务器响应动态调整重试次数和延迟时间。例如,如果服务器返回的错误表明是临时性问题(如 503 状态码),可以适当增加重试次数;如果网络不稳定,可以动态调整延迟时间。 - 示例代码:
@interface NetworkManager : NSObject
@property (nonatomic, assign) NSInteger baseRetryCount;
@property (nonatomic, assign) NSTimeInterval baseInitialDelay;
- (void)sendRequest;
@end
@implementation NetworkManager
- (instancetype)init {
self = [super init];
if (self) {
self.baseRetryCount = 3;
self.baseInitialDelay = 1;
}
return self;
}
- (void)sendRequest {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
NSInteger retryCount = self.baseRetryCount;
NSTimeInterval initialDelay = self.baseInitialDelay;
if (error) {
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorTimedOut) {
// 超时错误,增加重试次数
retryCount += 2;
initialDelay *= 1.5;
} else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCannotConnectToHost) {
// 无法连接错误,减少延迟时间
initialDelay /= 2;
}
} else if (response) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 503) {
// 服务器 503 错误,增加重试次数
retryCount += 3;
initialDelay *= 2;
}
}
if (retryCount > 0) {
NSTimeInterval delay = initialDelay * pow(2, (double)(retryCount - 1));
NSLog(@"请求失败,重试次数: %ld,延迟 %.2f 秒后重试", (long)retryCount, delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self sendRequest];
});
} else {
NSLog(@"重试次数用尽,请求失败: %@", error);
}
}
}];
[task resume];
}
@end
- 在上述代码中,`NetworkManager` 类根据不同的错误类型和服务器响应状态码动态调整重试次数和初始延迟时间。如遇到超时错误,增加重试次数并适当增大初始延迟时间;遇到无法连接错误,减少初始延迟时间;遇到服务器 503 错误,大幅增加重试次数和初始延迟时间。这样可以根据实际情况更灵活地调整重试策略,提高请求的成功率。
通过合理运用上述超时与重试策略,结合实际应用场景进行优化,我们能够在 Objective - C 开发中构建更加稳定、高效的网络请求功能,提升应用的用户体验。无论是简单的单请求场景,还是复杂的并发请求与缓存结合场景,都可以通过精心设计的策略来应对网络不稳定带来的挑战。在实际开发过程中,需要不断测试和调整这些策略,以适应不同的网络环境和业务需求。同时,随着技术的不断发展,新的网络框架和优化方法可能会出现,开发者需要持续关注并学习,以保持应用的竞争力。
在处理网络请求的重试与超时过程中,还需要注意内存管理。例如,在使用 NSCache
进行缓存时,要确保缓存的数据不会导致内存泄漏。另外,在进行多次重试时,要及时释放不再使用的资源,如 NSURLSessionDataTask
对象。如果任务被取消或完成,应避免对其进行不必要的引用。
对于大型应用,建议将网络请求相关的逻辑封装成独立的模块,便于管理和维护。可以通过协议(protocol)来定义网络请求的接口,不同的实现类根据具体需求(如不同的重试与超时策略)来实现这些接口。这样可以提高代码的可扩展性和可维护性。例如:
@protocol NetworkRequestProtocol <NSObject>
- (void)sendRequest;
@end
@interface DefaultNetworkRequest : NSObject <NetworkRequestProtocol>
@property (nonatomic, assign) NSInteger retryCount;
@property (nonatomic, assign) NSTimeInterval initialDelay;
@end
@implementation DefaultNetworkRequest
- (void)sendRequest {
// 实现网络请求逻辑,包含重试与超时处理
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
// 处理响应数据
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response: %@", responseString);
} else {
if (self.retryCount > 0) {
if ([error.domain isEqualToString:NSURLErrorDomain] && (error.code == NSURLErrorTimedOut || error.code == NSURLErrorCannotConnectToHost)) {
NSTimeInterval delay = self.initialDelay * pow(2, (double)(self.retryCount - 1));
NSLog(@"请求失败(超时或连接失败),重试次数: %ld,延迟 %.2f 秒后重试", (long)self.retryCount, delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.retryCount--;
[self sendRequest];
});
} else {
NSLog(@"其他错误,不再重试: %@", error);
}
} else {
NSLog(@"重试次数用尽,请求失败: %@", error);
}
}
}];
[task resume];
}
@end
@interface CustomNetworkRequest : NSObject <NetworkRequestProtocol>
// 可以有不同的属性和实现
@end
@implementation CustomNetworkRequest
- (void)sendRequest {
// 不同的网络请求实现,可能有不同的重试与超时策略
}
@end
在实际使用中,可以根据需要选择不同的实现类:
DefaultNetworkRequest *defaultRequest = [[DefaultNetworkRequest alloc] init];
defaultRequest.retryCount = 3;
defaultRequest.initialDelay = 1;
[defaultRequest sendRequest];
CustomNetworkRequest *customRequest = [[CustomNetworkRequest alloc] init];
[customRequest sendRequest];
这样通过协议和不同的实现类,可以灵活地管理和切换网络请求的重试与超时策略,使代码结构更加清晰,易于扩展和维护。
在处理网络请求时,还需要考虑安全性。对于涉及用户敏感信息的请求,要确保使用 HTTPS 协议,并对请求和响应数据进行加密处理。在重试过程中,同样要保证数据的安全性。例如,可以使用 AFNetworking
等第三方框架,它提供了方便的 HTTPS 配置和数据加密功能。
另外,在应用进入后台时,需要妥善处理正在进行的网络请求。可以暂停请求,待应用回到前台时根据情况决定是否继续重试。例如,可以通过监听 UIApplicationDidEnterBackground
和 UIApplicationWillEnterForeground
通知来实现:
@interface NetworkManager : NSObject
@property (nonatomic, strong) NSMutableArray<NSURLSessionDataTask *> *tasks;
@end
@implementation NetworkManager
- (instancetype)init {
self = [super init];
if (self) {
self.tasks = [NSMutableArray array];
}
return self;
}
- (void)sendRequest {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 处理响应或错误
[self.tasks removeObject:task];
}];
[self.tasks addObject:task];
[task resume];
}
- (void)pauseRequests {
for (NSURLSessionDataTask *task in self.tasks) {
[task suspend];
}
}
- (void)resumeRequests {
for (NSURLSessionDataTask *task in self.tasks) {
[task resume];
}
}
@end
// 在 AppDelegate 中监听通知
- (void)applicationDidEnterBackground:(UIApplication *)application {
NetworkManager *manager = [NetworkManager sharedManager];
[manager pauseRequests];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NetworkManager *manager = [NetworkManager sharedManager];
[manager resumeRequests];
}
通过上述方式,可以在应用进入后台时暂停网络请求,避免不必要的资源消耗和网络流量浪费,待应用回到前台时继续进行请求,保证网络请求的连贯性。
在处理网络请求的重试与超时过程中,日志记录也是非常重要的。通过详细的日志记录,可以方便地排查问题。例如,记录每次请求的 URL、请求时间、响应时间、错误信息以及重试次数和延迟时间等。可以使用 NSLog
或者专门的日志框架(如 CocoaLumberjack
)来实现。
#import "DDLog.h"
#import "DDTTYLogger.h"
// 初始化日志框架
[DDLog addLogger:[DDTTYLogger sharedInstance]];
@interface NetworkManager : NSObject
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay;
@end
@implementation NetworkManager
- (void)sendRequestWithRetryCount:(NSInteger)retryCount initialDelay:(NSTimeInterval)initialDelay {
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSDate *startDate = [NSDate date];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDate *endDate = [NSDate date];
NSTimeInterval duration = [endDate timeIntervalSinceDate:startDate];
if (!error && data) {
DDLogInfo(@"Request to %@ success. Duration: %.2f seconds", url, duration);
// 处理响应数据
} else {
if (retryCount > 0) {
if ([error.domain isEqualToString:NSURLErrorDomain] && (error.code == NSURLErrorTimedOut || error.code == NSURLErrorCannotConnectToHost)) {
NSTimeInterval delay = initialDelay * pow(2, (double)(retryCount - 1));
DDLogWarn(@"Request to %@ failed (timeout or connection error). Retry count: %ld. Delay: %.2f seconds", url, (long)retryCount, delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self sendRequestWithRetryCount:retryCount - 1 initialDelay:initialDelay];
});
} else {
DDLogError(@"Request to %@ other error: %@", url, error);
}
} else {
DDLogError(@"Request to %@ failed after retries: %@", url, error);
}
}
}];
[task resume];
}
@end
通过上述日志记录,可以清晰地了解网络请求的执行情况,有助于快速定位和解决问题。同时,在发布应用时,可以根据需要调整日志的级别,避免过多的日志信息影响应用性能。
综上所述,在 Objective - C 中实现网络请求的重试与超时处理,需要综合考虑多方面的因素,包括网络类型、用户体验、服务器配合、安全性、应用状态以及日志记录等。通过合理的策略和优化技巧,可以构建出稳定、高效且用户体验良好的网络请求功能,为应用的成功奠定坚实基础。在实际开发中,要根据具体的业务需求和应用场景,灵活运用这些方法,并不断优化和改进,以适应不断变化的网络环境和用户需求。