ios - 通过重试将 NSOperation 子类化为 Internet 操作

标签 ios objective-c multithreading nsoperation

我正在将 NSOperation 子类化为后台线程中的 http 帖子。
那些特定的 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确定连接并响应通知以在连接恢复时自动重试,而不是简单地按规定的重试间隔数学级数重试。

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

  • 一些不相​​关的观察:
  • 注意,我删除了 dispatch_asyncmain 中发出请求到后台队列,因为您已经在使用异步方法(即使您没有使用,您也可能已将此操作添加到后台队列中,无论如何)。
  • 我还删除了 try/catch逻辑,因为与其他语言/平台不同,异常处理不是处理运行时错误的首选方法。通常 Cocoa 中的运行时错误是通过 NSError 处理的.在 Cocoa 中,异常通常仅用于处理程序员错误,而不是处理用户可能遇到的运行时错误。见苹果的讨论Dealing with Errors在使用 Objective-C 编程指南中。
  • 您可以摆脱手动实现的 isExecutingisFinished getter 方法,如果您只是在它们各自的声明期间为您的属性定义适当的 getter 方法:
    @property (nonatomic, readwrite, getter=isExecuting) BOOL executing;
    @property (nonatomic, readwrite, getter=isFinished)  BOOL finished;
    
  • 不过,您可能想编写自己的 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你已经分散了你的代码。
  • 另外,我认为您不需要实现 isCancelled方法(因为已经为您实现了)。但你真的应该覆盖 cancel调用其 super 的方法执行,但也会取消您的网络请求并完成您的操作。或者,而不是实现 cancel方法,您可以移至 delegate基于网络请求的再现,但一定要检查 [self isCancelled]didReceiveData方法。

    isCompletedisFinished 让我觉得多余.看来你可以完全消除completed属性(property)和isCompleted方法。
  • 通过同时支持 NSURLSession,您可能会不必要地重复网络代码量。和 NSURLConnection .如果您真的愿意,您可以这样做,但他们向我们保证 NSURLConnection仍然支持,所以我觉得它没有必要(除非你想享受一些适用于 iOS 7+ 设备的 NSURLSession 特定功能,而你目前没有这样做)。随心所欲,但就我个人而言,我正在使用 NSURLConnection我需要支持较早的 iOS 版本,以及 NSURLSession在我不这样做的地方,但除非有一些令人信服的业务要求,否则我不会倾向于同时实现两者。
  • 关于ios - 通过重试将 NSOperation 子类化为 Internet 操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22127427/

    相关文章:

    android - 无法使用适用于 Android 4 的代码在未调用 Looper.prepare() 的线程内创建处理程序

    iphone - Objective-C:查找字符串中的辅音

    objective-c - 添加匹配样式的按钮/操作

    ios - 在 iOS 上检查位置服务权限

    iphone - 等待多个异步网络调用,然后对其结果执行操作

    ios - 将非常大的 NSDecimal 转换为字符串,例如。 400,000,000,000 -> 400 T 等等

    multithreading - Spring @Async 限制线程数

    c++ - vector 的平行和

    ios - 在 UITableView 中搜索“意外发现 nil”

    iphone - 操作简单的 Core Data 对象时崩溃