通过重试将NSOperation子类化为互联网操作 [英] Subclassing NSOperation to internet operations with retry

查看:94
本文介绍了通过重试将NSOperation子类化为互联网操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为后台线程中的http post子类化NSOperation。
这些特定的http帖子不需要返回任何值。

I'm subclassing NSOperation for http post in background thread. Those specific http posts doesn't require any value to return.

我想要做的是当我遇到错误或超时时我想要它会在增加延迟后发送(斐波那契)。

What I'm trying to do is when I've an error or timeout I want it to send after an increasing delay (fibonacci).

到目前为止,我已经完成了这个:

So far I've done this:

NSInternetOperation .h:

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:

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. 在解决重试逻辑之前,您应该将呼叫转移到 [self completeOperation] 里面 NSURLSessionDataTask 的完成块sendAsynchronousRequest 。您当前的操作类过早完成(因此不会尊重依赖项和网络操作队列的预期 maxConcurrentOperationCount )。

  1. Before you tackle the retry logic, you should probably move your call to [self completeOperation] to inside the completion block of the NSURLSessionDataTask or sendAsynchronousRequest. Your current operation class is completing prematurely (and therefore would not honor dependencies and your network operation queue's intended maxConcurrentOperationCount).

重试逻辑似乎并不起眼。也许类似于:

The retry logic seems unremarkable. Perhaps something like:

- (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];
            });
        }
    }];
}


  • 就个人而言,我对这整个过程都很谨慎。我觉得你应该根据错误的类型使用逻辑。值得注意的是,如果由于缺少互联网连接而导致错误失败,则应使用可达性,用于确定连接并响应通知,以便在恢复连接时自动重试,而不是仅按重试间隔的规定数学级别重试。

  • Personally, I'm wary about this entire endeavor. It strikes me that you should be employing logic conditional upon the type of error. Notably, if the error is a failure resulting from lacking of internet connectivity, you should use Reachability to determine connectivity and respond to notifications to retry automatically when connectivity is restored, not simply retrying at prescribed mathematical progression of retry intervals.

    其他网络连接(使用Reachability可以更好地解决),我不清楚其他网络故障是否需要重试逻辑。

    Other than network connectivity (which is better addressed with Reachability), I'm unclear as to what other network failures warrant a retry logic.

    一些不相关的观察结果:

    Some unrelated observations:


    1. 注意,我删除了 dispatch_async main 中向后台队列发出请求,因为你已经使用了异步方法(即使你没有使用异步方法,你可能已经添加了这个无论如何,操作到后台队列。

    1. Note, I eliminated the dispatch_async of the issuing of the request in main to a background queue because you're using asynchronous methods already (and even if you weren't, you've presumably added this operation to a background queue, anyway).

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

    I've also removed the try/catch logic because, unlike other languages/platforms, exception handling is not the preferred method of handling runtime errors. Typically runtime errors in Cocoa are handled via NSError. In Cocoa, exceptions are generally used solely to handle programmer errors, but not to handle the runtime errors that a user would encounter. See Apple's discussion Dealing with Errors in the Programming with Objective-C guide.

    您可以摆脱手动实现的 isExecuting isFinished getter方法,如果您只是在各自的声明中为您的属性定义适当的getter方法:

    You can get rid of your manually implemented isExecuting and isFinished getter methods if you just define the appropriate getter method for your properties during their respective declarations:

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


  • 但是,您可能希望编写自己的 setExecuting setFinished setter方法,如果你愿意,可以为你做通知,例如:

  • You might, though, want to write your own setExecuting and setFinished setter methods, which do the notification for you, if you want, e.g.:

    @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

    Then, when you use the setter it will do the notifications for you, and you can remove the willChangeValueForKey and didChangeValueForKey that you have scattered about your code.

    另外,我认为您不需要实现 isCancelled 方法(因为已经为您实现了)。但你真的应该覆盖一个 cancel 方法,该方法调用其 super 实现,但也会取消你的网络请求并完成你的操作。或者,而不是实现取消方法,您可以移动到基于委托的网络请求再现,但请确保在 didReceiveData 方法中检查 [self isCancelled]

    Also, I don't think you need to implement isCancelled method (as that's already implemented for you). But you really should override a cancel method which calls its super implementation, but also cancels your network request and completes your operation. Or, instead of implementing cancel method, you could move to the delegate based rendition of the network requests but make sure you check for [self isCancelled] inside the didReceiveData method.

    isCompleted 使用 isFinished 对我造成多余影响。看起来您可以完全消除已完成的属性和 isCompleted 方法。

    And isCompleted strikes me as redundant with isFinished. It seems like you could entirely eliminate completed property and isCompleted method.

    您可能通过支持 NSURLSession NSURLConnection 来不必要地重复网络代码量。如果你真的想要,你可以这样做,但是他们向我们保证仍然支持 NSURLConnection ,所以它让我感到不必要(除非你想要享受一些 NSURLSession iOS 7+设备的特定功能,您目前没有这些功能。做你想做的事,但就个人而言,我正在使用 NSURLConnection 我需要支持早期的iOS版本, NSURLSession 除非有一些令人信服的业务要求,否则我不愿意实施这两项工作。

    You're probably unnecessarily duplicating the amount of network code by supporting both NSURLSession and NSURLConnection. You can do that if you really want, but they assure us that NSURLConnection is still supported, so it strikes me as unnecessary (unless you wanted to enjoy some NSURLSession specific features for iOS 7+ devices, which you're not currently doing). Do whatever you want, but personally, I'm using NSURLConnection where I need to support earlier iOS versions, and NSURLSession where I don't, but I wouldn't be inclined to implement both unless there was some compelling business requirement to do so.

    这篇关于通过重试将NSOperation子类化为互联网操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆