【问题标题】:NSOperationQueue run same task again on response iOSNSOperationQueue 在响应 iOS 上再次运行相同的任务
【发布时间】:2014-08-27 10:49:39
【问题描述】:

在我的项目中,我需要向服务器发送数据,为此我使用了以下代码来完成任务:

- (void)sendJSONToServer:(NSString *) jsonString
{
// Create a new NSOperationQueue instance.
operationQueue = [NSOperationQueue new];
//

// Create a new NSOperation object using the NSInvocationOperation subclass to run the operationQueueTask method
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                        selector:@selector(operationQueueTask:)
                                                                        object:jsonString];
// Add the operation to the queue and let it to be executed.
[operationQueue addOperation:operation];
}//End of sendJSONToServer method

-(void) operationQueueTask:(NSString *) jsonString
{
//NSOperationQueue *remoteResultQueue = [[NSOperationQueue alloc] init];
dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
dispatch_async(myQueue, ^{
    // Performing long running process
    // Sending json data to server asynchronously
    NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[jsonString length]];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"MY_URL_eg_http://www.example.com"]];

    [request setHTTPMethod:@"POST"];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:postData];

    [NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         NSLog(@"Response is:%@",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
     }];

    dispatch_async(dispatch_get_main_queue(), ^{
        // Update the UI
        NSLog(@"Thread Process Finished");
    });
});
}//End of operationQueueTask method

通过上面的代码,我可以发送数据并得到响应。

但是当没有互联网时,数据不会被发送到服务器。如何根据我们得到的响应来处理这种情况。

假设我们在公平条件下收到回复 success,在最差条件下收到回复 false


重试的更新代码

-(id)init
{
self = [super init];
if (self != nil)
{
    //initialize stuffs here
    pendingOperationQueue = [[NSMutableArray alloc] init];
    operationQueue = [NSOperationQueue new];
}
return self;
}//End of init method

- (void)sendJSONToServer:(NSString *) jsonString
{
    NSOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationQueueTask:) object:[NSString stringWithString:[pendingOperationQueue objectAtIndex:0]]];
[operation start];
}//End of sendJSONToServer method

-(void) operationQueueTask:(NSString *) jsonString
{
//NSOperationQueue *remoteResultQueue = [[NSOperationQueue alloc] init];
dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
dispatch_async(myQueue, ^{
    // Performing long running process
    // Sending json data to server asynchronously
    NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[jsonString length]];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"MY_URL_http://www/example.com"]];

    [request setHTTPMethod:@"POST"];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:postData];

    [NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         NSLog(@"Response is:%@",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);

         if([[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] rangeOfString:@"true"].location == NSNotFound)
         {
             // Add the operation to the queue and let it to be executed.
             NSLog(@"Failed To Add To Server, Rerunning the task");
         }
         else
         {
             NSLog(@"Successfully Added To Server");
             NSLog(@"ADDED_DATA_TO_SERVER: %@", jsonString);
             if([pendingOperationQueue count] > 0)
             {
                 [pendingOperationQueue removeObjectAtIndex:0];

                 if([pendingOperationQueue count] > 0)
                 {
                     NSOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationQueueTask:) object:[NSString stringWithString:[pendingOperationQueue objectAtIndex:0]]];
                     [operation start];
                 }
             }
         }
     }];
});
}//End of operationQueueTask method

【问题讨论】:

    标签: ios iphone nsoperation nsoperationqueue


    【解决方案1】:

    注意!这是一个很长的答案。 TL;DR:你不能重新运行NSOperation,但你可以设计你的类和方法,以便于重试请求。


    首先快速回答您的标题问题:您不能重新运行NSOperation,它们并非旨在这样做。来自docs

    一个操作对象是一个单次对象——也就是说,它执行它的 任务一次,不能再次执行。

    除此之外,让我们看看您当前正在做什么,然后稍微清理一下,以便更轻松地重新使用它。那里有很多你不需要的异步东西;我会一步一步来的。

    让我们从您的operationQueueTask: 方法开始。你在方法中做的第一件事是:

    dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
    

    这意味着每次调用该方法时,您都会创建一个新的调度队列。如果您真的愿意,您可以这样做,但这并不是调度队列的真正设计目的。一个更好的主意是使用一个已经可用的后台队列:

    dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    

    接下来,您将异步分派一个块到该队列。那块:

    1. 设置您的NSMutableURLRequest
    2. 致电[NSURLConnection sendAsynchronousRequest:...]
    3. 将另一个块(其中包含有关更新 UI 的注释)分派到主队列。

    1 和 2 没问题,我看不出你需要在那里改变什么。但是,由于调用调度的位置,3 是有问题的。按照您现在设置的方式,NSURLConnection 将触发其异步请求,然后,在此之前甚至有机会运行,您将块触发到主队列以更新 UI。您需要做的是在传递给[NSURLConnection sendAsynchronousRequest:...] 的完成处理程序中触发该块。像这样:

    [NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         NSLog(@"Response is:%@",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
         dispatch_async(dispatch_get_main_queue(), ^{
             // Update the UI
             NSLog(@"Thread Process Finished");
         });
     }];
    

    现在,请注意您在NSURLConnection 上调用的方法的名称? sendAsynchronousRequest:。它实际上为您处理在后台队列中排队请求。这意味着,您实际上并不需要(或想要)此方法开头的所有 dispatch_* 内容。考虑到这一点,我们可以将其简化为:

    -(void) operationQueueTask:(NSString *) jsonString
    {
        NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
        NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[jsonString length]];
    
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"MY_URL_eg_http://www.example.com"]];
    
        [request setHTTPMethod:@"POST"];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:postData];
    
        [NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
         {
             NSLog(@"Response is:%@",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
             dispatch_async(dispatch_get_main_queue(), ^{
                 // Update the UI
                 NSLog(@"Thread Process Finished");
             });
         }];
    } //End of operationQueueTask method
    

    现在,转到您的 sendJSONToServer: 方法。您在这里做的事情与您在operationQueueTask: 开始时所做的类似:每次运行时您都在创建一个新的NSOperationQueue;这也不是必需的(通常也不需要)。您可能应该做的是在您的类初始化时创建 operationQueue(看起来它已经是您的类上的一个实例变量,所以您很好):

    // NOTE: I'm just using a default initializer here; if you already have an initializer, use that instead
    - (instancetype)init {
        if (self = [super init]) {
            operationQueue = [NSOperationQueue new];
        }
        return self;
    }
    

    这摆脱了你的第一行。接下来,您将创建一个调用operationQueueTask:NSInvocationOperation,然后将其添加到您的operationQueue。由于您每次都在重新创建您的operationQueue,我将假设它不用于这些服务器请求之外的任何其他内容。在这种情况下,您实际上根本不需要在您的operationQueue 上执行此操作,因为正如我们在前面的方法中发现的那样,NSURLConnection 已经为您处理了所有后台线程。在那种的情况下,我们实际上可以将代码从operationQueueTask: 复制到sendJSONToServer: 并完全摆脱operationQueueTask:。这使它看起来像:

    - (void)sendJSONToServer:(NSString*)jsonString {
        NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
        NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[jsonString length]];
    
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"MY_URL_eg_http://www.example.com"]];
    
        [request setHTTPMethod:@"POST"];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:postData];
    
        [NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
         {
             NSLog(@"Response is:%@",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
             dispatch_async(dispatch_get_main_queue(), ^{
                 // Update the UI
                 NSLog(@"Thread Process Finished");
             });
         }];
    }
    

    注意:我们仍然需要保留 operationQueue,因为我们将它传递给 [NSURLConnection sendAsynchronousRequest:... 作为它应该运行的队列。

    那么,当请求失败时,我们如何重试请求呢?最简单的方法是添加一个递归函数,在请求失败时调用自身。您将向此方法传递您要发送的jsonString,以及它在永久放弃之前尝试发送它的最大次数。

    为方便起见,让我们对现有函数再做一次更改:不是在函数内部处理完成块,而是让完成块成为传递给函数的参数,以便可以在其他地方处理它。

    - (void)sendJSONToServer:(NSString*)jsonString withCompletionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *connectionError))completionHandler {
        NSData *postData = [jsonString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
        NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[jsonString length]];
    
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"MY_URL_eg_http://www.example.com"]];
    
        [request setHTTPMethod:@"POST"];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:postData];
    
        [NSURLConnection sendAsynchronousRequest:request queue:operationQueue completionHandler:completionHandler];
    }
    

    现在,让我们构建递归函数。我会这样称呼它:

    - (void)sendJSONToServer:(NSString*)jsonString withRetryAttempts:(NSUInteger)retryTimes;
    

    基本流程是:

    1. 检查retryTimes是否大于0
    2. 如果是,请尝试将请求发送到服务器
    3. 请求完成后,检查响应是否成功
    4. 如果成功,更新主队列上的 UI
    5. 如果不成功,将retryTimes减一并再次调用该函数

    看起来像:

    - (void)sendJSONToServer:(NSString*)jsonString withRetryAttempts:(NSUInteger)retryTimes {
        if (retryTimes > 0) {
            [self sendJSONToServer:jsonString withCompletionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                NSLog(@"Response is:%@",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
                if (/* check response to make sure it succeeded */) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        // Update the UI
                        NSLog(@"Thread Process Finished");
                    });
                } else {
                    // Note: you can add a dispatch_after here (or something similar) to wait before the next attempt
                    // You could also add exponential backoff here, which is usually good when retrying network stuff
                    [self sendJSONToServer:jsonString withRetryAttempts:(retryTimes - 1)];
                }
            }];
        } else {
            // We're out of retries; handle appropriately
        }
    }
    

    注意:其中有些位只是 cmets,因为它们是特定于应用程序的;它们需要在该代码编译/运行之前实现。

    现在,不要调用[yourClass sendJSONToServer:jsonString],而是调用:[yourClass sendJSONToServer:jsonString withRetryTimes:maxRetries],如果请求失败,它应该重试maxRetries次。

    最后一点:正如@Deftsoft 提到的,Apple 的 Reachability 类是了解您是否与网络连接的好方法。在尝试致电sendJSONToServer:withRetryTimes: 之前先检查一下是个好主意。这样一来,您就不会在无法连接时尝试发出请求。

    【讨论】:

    • +1 简要回答,感谢您的时间回答,我从您的回答中学到了很多东西,但不幸的是,如果我们在某个时候继续重试,即使结果是失败,或者如果我们继续重试更长的时间,那就不好了,对吧?请发表评论以获取建议。谢谢。
    • 如果你的所有 reties 都失败了你会做什么是非常特定于应用程序的,但是做了很多的一件事是将你发送的 json 存储在某个地方(通常是一个文件),然后尝试再次发送它当某些情况发生变化时(例如应用重新启动或网络发生变化)。
    • 这是个好主意,我想分享我一直在努力的重试更新代码,如果你不介意请看一下,如果我是,请纠正我错了。
    • 一些快速观察: 1. 我看不到您在哪里添加任何东西到pendingOperationQueue。 2.我没看到你在哪里调用sendJSONToServer触发重试。
    【解决方案2】:

    您可以在下面的 Apple 可达性类是参考代码,它将为您提供更好的想法。

     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkChanged:) name:kReachabilityChangedNotification object:nil];
    
            reachability = [Reachability reachabilityForInternetConnection];
            [reachability startNotifier];
    
            - (void)networkChanged:(NSNotification *)notification
            {
    
              NetworkStatus remoteHostStatus = [reachability currentReachabilityStatus];
    
              if(remoteHostStatus == NotReachable) { NSLog(@"not reachable");}
              else if (remoteHostStatus == ReachableViaWiFiNetwork) { NSLog(@"wifi"); }
              else if (remoteHostStatus == ReachableViaCarrierDataNetwork) { NSLog(@"carrier"); }
            }
    

    【讨论】:

    • 感谢您的宝贵回答,真的很好,但是我的问题是如何重新运行操作,而不是检查网络变化状态,请告诉我们这个谜语。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-10-25
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多