NSOperationQueue在响应iOS上再次运行相同的任务

Che*_*tty 2 iphone nsoperation nsoperationqueue ios

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

- (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
Run Code Online (Sandbox Code Playgroud)

通过上面的代码,我能够发送数据并获得响应.

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

假设我们在最恶劣的条件下得到了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
Run Code Online (Sandbox Code Playgroud)

Mik*_*e S 6

当心!这是一个很长的答案. TL; DR:您无法重新运行NSOperation,但您可以设计类和方法,以便轻松重试请求.


首先快速回答你的标题问题:你不能重新运行NSOperation,他们不是为了那样做.来自文档:

操作对象是单击对象 - 也就是说,它执行一次任务,不能再用于执行它.

有了这个,让我们来看看你目前正在做什么,并清理一下,以便重新使用它更容易.那里有很多你不需要的异步东西; 我会一点一点地完成它.

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

dispatch_queue_t myQueue = dispatch_queue_create("SERVER_QUEUE",NULL);
Run Code Online (Sandbox Code Playgroud)

这意味着每次调用该方法时,您都在创建一个新的调度队列.虽然你可以这样做,但如果你真的想要,那不是调度队列的真正设计目的.更好的想法是使用已经可用的后台队列之一:

dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
Run Code Online (Sandbox Code Playgroud)

接下来,您将异步调度块到该队列.那块:

  1. 设置你的NSMutableURLRequest.
  2. 打电话[NSURLConnection sendAsynchronousRequest:...].
  3. 将另一个块(其中有关于更新UI的注释)调度到主队列.

1和2都很好,我没有看到你需要改变的东西.但是,由于调用该调度的位置,因此存在问题.现在设置它的方式,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");
     });
 }];
Run Code Online (Sandbox Code Playgroud)

现在,请注意您正在呼叫的方法的名称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
Run Code Online (Sandbox Code Playgroud)

现在,关于你的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;
}
Run Code Online (Sandbox Code Playgroud)

这摆脱了你的第一线.接下来,您正在创建一个NSInvocationOperation调用operationQueueTask:,然后将其添加到您的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");
         });
     }];
}
Run Code Online (Sandbox Code Playgroud)

注意:我们仍然需要保留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];
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们构建递归函数.我会称之为:

- (void)sendJSONToServer:(NSString*)jsonString withRetryAttempts:(NSUInteger)retryTimes;
Run Code Online (Sandbox Code Playgroud)

基本流程将是:

  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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

最后一点:正如@Deftsoft所提到的,Apple的Reachability类是一种很好的方式来了解你是否有与网络的有效连接.在尝试打电话之前先检查一下是个好主意sendJSONToServer:withRetryTimes:.这样,当你无法连接时,你就不会尝试发出请求.