将NSOperationinheritance到Internet操作并重试

我在后台线程中inheritancehttp post的NSOperation。 这些特定的httppost不需要任何值返回。

我想要做的是当我有一个错误或超时,我希望它发送后延长(斐波那契)。

到目前为止,我已经这样做了:

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 

一些反应:

  1. 在处理重试逻辑之前,您应该将您的调用移动到NSURLSessionDataTasksendAsynchronousRequest的完成块内部 [self completeOperation] 。 您当前的操作类过早完成(因此不会承诺依赖性和您的networking操作队列预期的maxConcurrentOperationCount )。

  2. 重试逻辑似乎不起眼。 也许是这样的:

     - (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]; }); } }]; } 
  3. 就我个人而言,我对整个努力都很谨慎。 这让我觉得你应该在错误types的条件下使用逻辑。 值得注意的是,如果错误是因缺less互联网连接而导致的失败,则应该使用Reachability来确定连接性 ,并在连接恢复时自动响应通知以便重试,而不是简单地按规定的重试间隔的math级数重试。

    除了networking连接(这可以通过Reachability更好地解决)之外,我还不清楚哪些networking故障可以保证重试逻辑。

一些无关的观察:

  1. 注意,由于你已经使用了asynchronous方法(即使你不是,你可能已经把这个操作添加到了后台队列中),我将main请求发出的dispatch_async消除了。

  2. 我还删除了try / catch逻辑,因为与其他语言/平台不同,exception处理不是处理运行时错误的首选方法。 通常,Cocoa中的运行时错误是通过NSError来处理的。 在Cocoa中,exception通常仅用于处理程序员错误,但不处理用户遇到的运行时错误。 请参阅Apple的讨论使用Objective-C指南处理 编程中的 错误 。

  3. 如果您只是在其各自的声明中为您的属性定义适当的getter方法,则可以摆脱手动执行的isExecutingisFinished getter方法:

     @property (nonatomic, readwrite, getter=isExecuting) BOOL executing; @property (nonatomic, readwrite, getter=isFinished) BOOL finished; 
  4. 不过,你可能想要编写你自己的setExecutingsetFinished 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时,它会为你做通知,你可以删除你散布的代码中的willChangeValueForKeydidChangeValueForKey

  5. 另外,我不认为你需要实现isCancelled方法(因为这已经为你实现)。 但是你真的应该重写一个cancel方法来调用它的super实现,同时也取消你的networking请求并完成你的操作。 或者,不是实现cancel方法,而是移动到networking请求的基于delegate的再现,但是确保在[self isCancelled]方法内检查[self isCancelled]

    完成后,我觉得自己已经完成了。 看来你可以完全消除completed属性和isCompleted方法。

  6. 您可能通过支持NSURLSessionNSURLConnection不必要地复制大量的networking代码。 如果你真的想要的话,你可以这样做,但是他们向我们保证NSURLConnection仍然是支持的,所以我觉得这是不必要的(除非你想享受一些iOS 7+设备的NSURLSession特性)。 做任何你想做的事情,但是我个人使用的是NSURLConnection ,我需要支持早期的iOS版本,而NSURLSession则不支持,但是除非有一些令人信服的业务要求,否则我不会倾向于实现。 。

你的方法:

 static NSString * const kFinishedKey = @"isFinished"; static NSString * const kExecutingKey = @"isExecuting"; - (void)completeOperation { [self willChangeValueForKey:kFinishedKey]; [self willChangeValueForKey:kExecutingKey]; self.executing = NO; self.finished = YES; [self didChangeValueForKey:kExecutingKey]; [self didChangeValueForKey:kFinishedKey]; } 

手动发送关键path“isFinished”和“isExecuting”的通知。 NSOperationQueue观察这些状态的“完成”和“执行”的关键path – “isFinished”和“isExecuting”是这些属性的get(读)访问器的名称。

这些不是你要找的关键路径

对于一个NSOperation子类,KVO通知应该自动发送,除非你的类已经通过+automaticallyNotifiesObserversForKey NSOperation或者+automaticallyNotifiesObserversOf<Key> NSOperation +automaticallyNotifiesObserversOf<Key>返回NO来退出自动KVO通知。 你可以在这里看到一个示例项目演示。

你的财产声明:

 @property (nonatomic) BOOL executing; @property (nonatomic) BOOL finished; @property (nonatomic) BOOL cancelled; 

NSOperation没有提供正确的get访问器, NSOperation覆盖NSOperation那些。 将这些更改为:

 @property (nonatomic, getter=isExecuting) BOOL executing; @property (nonatomic, getter=isFinished) BOOL finished; @property (nonatomic, getter=isCancelled) BOOL cancelled; 

NSOperation获得正确的行为。 NSOperation这些声明声明为只读公共接口,您可以select使它们在一个私有类扩展中进行readwrite

就实现与重试逻辑的连接而言,有一个很好的Apple示例代码项目,演示了MVCNetworking