将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
一些反应:
-
在处理重试逻辑之前,您应该将您的调用移动到
NSURLSessionDataTask
或sendAsynchronousRequest
的完成块内部[self completeOperation]
。 您当前的操作类过早完成(因此不会承诺依赖性和您的networking操作队列预期的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]; }); } }]; }
-
就我个人而言,我对整个努力都很谨慎。 这让我觉得你应该在错误types的条件下使用逻辑。 值得注意的是,如果错误是因缺less互联网连接而导致的失败,则应该使用Reachability来确定连接性 ,并在连接恢复时自动响应通知以便重试,而不是简单地按规定的重试间隔的math级数重试。
除了networking连接(这可以通过Reachability更好地解决)之外,我还不清楚哪些networking故障可以保证重试逻辑。
一些无关的观察:
-
注意,由于你已经使用了asynchronous方法(即使你不是,你可能已经把这个操作添加到了后台队列中),我将
main
请求发出的dispatch_async
消除了。 -
我还删除了
try
/catch
逻辑,因为与其他语言/平台不同,exception处理不是处理运行时错误的首选方法。 通常,Cocoa中的运行时错误是通过NSError
来处理的。 在Cocoa中,exception通常仅用于处理程序员错误,但不处理用户遇到的运行时错误。 请参阅Apple的讨论使用Objective-C指南处理 编程中的 错误 。 -
如果您只是在其各自的声明中为您的属性定义适当的getter方法,则可以摆脱手动执行的
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
实现,同时也取消你的networking请求并完成你的操作。 或者,不是实现cancel
方法,而是移动到networking请求的基于delegate
的再现,但是确保在[self isCancelled]
方法内检查[self isCancelled]
。完成后,我觉得自己已经完成了。 看来你可以完全消除
completed
属性和isCompleted
方法。 -
您可能通过支持
NSURLSession
和NSURLConnection
不必要地复制大量的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