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

时间:2014-08-27 10:49:40

标签: ios iphone nsoperation nsoperationqueue

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

- (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

2 个答案:

答案 0 :(得分:5)

抬起头来!这是一个很长的答案。 TL; DR:您无法重新运行NSOperation,但您可以设计类和方法,以便轻松重试请求。< / em>的


首先快速回答您的标题问题:您无法重新运行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的注释)调度到主队列。
  4. 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");
         });
     }];
    

    现在,请注意您在NSURLConnection上调用的方法的名称? send Asynchronous Request:。它实际上为您处理在后台队列上排队请求。这意味着,您实际上并不需要(或想要)此方法开头的所有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;
    }
    

    摆脱了你的第一线。接下来,您需要创建一个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");
             });
         }];
    }
    

    注意:我们仍然需要保留operationQueue,因为我们将其作为应运行的队列传递给[NSURLConnection sendAsynchronousRequest:... < / p>

    那么,我们如何在失败时重试请求呢?最简单的方法是添加一个递归函数,该函数在请求失败时调用自身。您将此方法传递给您要发送的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中减去一个并再次调用此函数
    6. 看起来像是:

      - (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
          }
      }
      

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

      现在,请拨打[yourClass sendJSONToServer:jsonString],而不是致电[yourClass sendJSONToServer:jsonString withRetryTimes:maxRetries],如果请求失败,则应重试maxRetries次。

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

答案 1 :(得分:0)

下面的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"); }
        }