我正在为后台线程中的 http 帖子子类化 NSOperation。
那些特定的 http 帖子不需要返回任何值。
我想要做的是当我遇到错误或超时时,我希望它在增加延迟(斐波那契)后发送。
到目前为止,我已经这样做了:
NSInternetOperation.h:
#import <Foundation/Foundation.h>
@interface NSInternetOperation : NSOperation
@property (nonatomic) BOOL executing;
@property (nonatomic) BOOL finished;
@property (nonatomic) BOOL completed;
@property (nonatomic) BOOL cancelled;
- (id)initWebServiceName:(NSString*)webServiceName andPerameters:(NSString*)parameters;
- (void)start;
@end
NSInternetOperation.m:
#import "NSInternetOperation.h"
static NSString * const kFinishedKey = @"isFinished";
static NSString * const kExecutingKey = @"isExecuting";
@interface NSInternetOperation ()
@property (strong, nonatomic) NSString *serviceName;
@property (strong, nonatomic) NSString *params;
- (void)completeOperation;
@end
@implementation NSInternetOperation
- (id)initWebServiceName:(NSString*)webServiceName andPerameters:(NSString*)parameters
{
self = [super init];
if (self) {
_serviceName = webServiceName;
_params = parameters;
_executing = NO;
_finished = NO;
_completed = NO;
}
return self;
}
- (BOOL)isExecuting { return self.executing; }
- (BOOL)isFinished { return self.finished; }
- (BOOL)isCompleted { return self.completed; }
- (BOOL)isCancelled { return self.cancelled; }
- (BOOL)isConcurrent { return YES; }
- (void)start
{
if ([self isCancelled]) {
[self willChangeValueForKey:kFinishedKey];
self.finished = YES;
[self didChangeValueForKey:kFinishedKey];
return;
}
// If the operation is not cancelled, begin executing the task
[self willChangeValueForKey:kExecutingKey];
self.executing = YES;
[self didChangeValueForKey:kExecutingKey];
[self main];
}
- (void)main
{
@try {
//
// Here we add our asynchronized code
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *completeURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", kWEB_SERVICE_URL, self.serviceName]];
NSData *body = [self.params dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:completeURL];
[request setHTTPMethod:@"POST"];
[request setValue:kAPP_PASSWORD_VALUE forHTTPHeaderField:kAPP_PASSWORD_HEADER];
[request setHTTPBody:body];
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)body.length] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
if (__iOS_7_AND_HIGHER)
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:[Netroads sharedInstance] delegateQueue:[NSOperationQueue new]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error)
{
NSLog(@"%@ Error: %@", self.serviceName, error.localizedDescription);
}
else
{
//NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//NSLog(@"\n\nResponseXML(%@):\n%@", webServiceName, responseXML);
}
}];
[dataTask resume];
}
else
{
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError)
{
NSLog(@"%@ Error: %@", self.serviceName, connectionError.localizedDescription);
}
else
{
//NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//NSLog(@"\n\nResponseXML(%@):\n%@", webServiceName, responseXML);
}
}];
}
});
[self completeOperation];
}
@catch (NSException *exception) {
NSLog(@"%s exception.reason: %@", __PRETTY_FUNCTION__, exception.reason);
[self completeOperation];
}
}
- (void)completeOperation
{
[self willChangeValueForKey:kFinishedKey];
[self willChangeValueForKey:kExecutingKey];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:kExecutingKey];
[self didChangeValueForKey:kFinishedKey];
}
@end
最佳答案
几个 react :
[self completeOperation]
的调用移动到 NSURLSessionDataTask
或 sendAsynchronousRequest
的完成块内。您当前的操作类过早完成(因此不会遵守依赖项和您的网络操作队列的预期 maxConcurrentOperationCount
)。 - (void)main
{
NSURLRequest *request = [self createRequest]; // maybe move the request creation stuff into its own method
[self tryRequest:request currentDelay:1.0];
}
- (void)tryRequest:(NSURLRequest *)request currentDelay:(NSTimeInterval)delay
{
[NSURLConnection sendAsynchronousRequest:request queue:[self networkOperationCompletionQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
BOOL success = NO;
if (connectionError) {
NSLog(@"%@ Error: %@", self.serviceName, connectionError.localizedDescription);
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode == 200) {
// parse XML response here; if successful, set `success` to `YES`
}
}
}
if (success) {
[self completeOperation];
} else {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
NSTimeInterval nextDelay = [self nextDelayFromCurrentDelay:delay];
[self tryRequest:request currentDelay:nextDelay];
});
}
}];
}
除了网络连接(使用 Reachability 可以更好地解决)之外,我不清楚哪些其他网络故障需要重试逻辑。
一些不相关的观察:
dispatch_async
中删除了向后台队列发出请求的 main
,因为您已经在使用异步方法(即使您没有使用异步方法,您也可能已将此操作添加到后台队列中,无论如何) . try
/catch
逻辑,因为与其他语言/平台不同,异常处理不是处理运行时错误的首选方法。通常 Cocoa 中的运行时错误是通过 NSError
处理的。在 Cocoa 中,异常通常仅用于处理程序员错误,而不是处理用户可能遇到的运行时错误。请参阅 Apple 在使用 Objective-C 编程指南中对 Dealing with Errors 的讨论。 isExecuting
和 isFinished
getter 方法:@property (nonatomic, readwrite, getter=isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter=isFinished) BOOL finished;
setExecuting
和 setFinished
setter 方法,如果需要,它们会为您执行通知,例如:@synthesize finished = _finished;
@synthesize executing = _executing;
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:kExecutingKey];
_executing = executing;
[self didChangeValueForKey:kExecutingKey];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:kFinishedKey];
_finished = finished;
[self didChangeValueForKey:kFinishedKey];
}
然后,当您使用 setter 时,它会为您发送通知,您可以删除散布在代码中的
willChangeValueForKey
和 didChangeValueForKey
。 isCancelled
方法(因为它已经为您实现了)。但是您确实应该覆盖 cancel
方法,该方法调用其 super
实现,同时取消您的网络请求并完成您的操作。或者,您可以移动到基于 cancel
的网络请求再现,而不是实现 delegate
方法,但请确保检查 [self isCancelled]
方法中的 didReceiveData
。我觉得
isCompleted
与 isFinished
是多余的。似乎您可以完全消除 completed
属性和 isCompleted
方法。 NSURLSession
和 NSURLConnection
,您可能会不必要地复制网络代码量。如果你真的愿意,你可以这样做,但他们向我们保证 NSURLConnection
仍然受支持,所以我觉得它没有必要(除非你想享受一些适用于 iOS 7+ 设备的 NSURLSession
特定功能,而你目前没有这样做)。做任何你想做的事,但就我个人而言,我在需要支持早期 iOS 版本的地方使用 NSURLConnection
,而在我不支持的地方使用 NSURLSession
,但除非有一些令人信服的业务需求,否则我不会倾向于实现两者. 关于ios - 通过重试将 NSOperation 子类化为 Internet 操作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22127427/