2

Currently I am queueing a simple offline request using AFHTTPRequestOperationManager and it doesn't seem to work in the desired manner:

Here is the responsible code and below are different execution patterns:

@interface ViewController ()
{
    AFHTTPRequestOperationManager *manager;
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    manager = [AFHTTPRequestOperationManager manager];
    NSOperationQueue *operationQueue = manager.operationQueue;

    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
        NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
        switch (status) {
            case AFNetworkReachabilityStatusReachableViaWWAN:
            case AFNetworkReachabilityStatusReachableViaWiFi:
                NSLog(@"Operation: %@", operationQueue.operations);
                [operationQueue setSuspended:NO];
                NSLog(@"ONLINE");
                break;
            case AFNetworkReachabilityStatusNotReachable:
            default:
                NSLog(@"Operation: %@", operationQueue.operations);
                [operationQueue setSuspended:YES];
                NSLog(@"OFFLINE");
                break;
        }
    }];

    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    [manager GET:@"http://www.google.com"
      parameters:nil
         success:^(AFHTTPRequestOperation *operation, id response){
             NSLog(@"success");
         }
         failure:^(AFHTTPRequestOperation *operation, NSError *failure){
             NSLog(@"failure");
         }];
}

Pattern 1:

  • Device on AirPlane mode
  • Run

Console output:

2015-03-21 16:03:54.486 OfflineSupport[928:227748] Reachability: Not Reachable
2015-03-21 16:03:54.494 OfflineSupport[928:227748] Operation: (
    "<AFHTTPRequestOperation: 0x1701d0c20, state: isExecuting, cancelled: NO request: <NSMutableURLRequest: 0x170014ab0> { URL: http://www.google.com }, response: (null)>"
)
2015-03-21 16:03:54.494 OfflineSupport[928:227748] OFFLINE
2015-03-21 16:03:54.544 OfflineSupport[928:227748] failure
  • Wifi Activated

Console output contd.:

2015-03-21 16:04:05.594 OfflineSupport[928:227748] Reachability: Reachable via WiFi
2015-03-21 16:04:05.595 OfflineSupport[928:227748] Operation: (
)
2015-03-21 16:04:05.595 OfflineSupport[928:227748] ONLINE

Pattern 2:

  • Wifi Active
  • Run

Console output:

2015-03-21 16:05:43.818 OfflineSupport[934:228478] Reachability: Reachable via WiFi
2015-03-21 16:05:43.826 OfflineSupport[934:228478] Operation: (
    "<AFHTTPRequestOperation: 0x1701dde20, state: isExecuting, cancelled: NO request: <NSMutableURLRequest: 0x17001ad10> { URL: http://www.google.com }, response: (null)>"
)
2015-03-21 16:05:43.826 OfflineSupport[934:228478] ONLINE
2015-03-21 16:05:43.960 OfflineSupport[934:228478] success
  • AirPlane activated

Console output contd.:

2015-03-21 16:05:53.437 OfflineSupport[934:228478] Reachability: Not Reachable
2015-03-21 16:05:53.438 OfflineSupport[934:228478] Operation: (
)
2015-03-21 16:05:53.438 OfflineSupport[934:228478] OFFLINE

In pattern 1, the request results in the failure block as there is no access. But when the device comes online, the request is not executed again. Is there something I am missing here? Do I have to configure something on the operation queue or in the failure block?

Reference: AFNetworking 2.0 queue request when device is offline with setReachabilityStatusChangeBlock does nothing, IOS - best way to queue requests to be sent when connection is reestablished

Community
  • 1
  • 1
p0lAris
  • 4,750
  • 8
  • 45
  • 80

1 Answers1

4

A few observations:

  1. In pattern 1, you have a bit of a race condition because the reachability status block runs asynchronously, so if you start reachability and immediately add operation, the status may not have been identified as being offline yet, and thus the queue may not have been suspended and thus the operation may start immediately (and fail because you're offline).

    The problem is solved if you suspend the queue before starting reachability and before starting any operations. If you're actually offline, the queue will stay offline and any operations that were added will be suspended, too. But if you were really online, the reachability block will be called reasonably quickly and and the queue will be promptly be unsuspended. It eliminates this race condition.

  2. The suspended state of a queue does not affect operations that have already started. In only impacts those operations that have not yet started. So, if the connection goes offline while a network operation was in progress, there is no built in mechanism to pause the operation until the connection is restored, nor restart the operation when the status changes. If you want that functionality, you'd have to implement that yourself.


A few more observations:

  1. It's worth noting, though, that just because reachability says that connectivity is available, it doesn't guarantee that the request will succeed. You still need to gracefully handle failed requests.

  2. To the prior point, if you want a more reliable "can I connect to a particular server", you might consider using managerForDomain rather than sharedManager. Just make sure to keep a strong reference to the resulting AFNetworkReachabilityManager, because unlike the singleton, it won't keep a strong reference to itself.

  3. The AFHTTPRequestOperationManager is from version 2.x, and you might consider upgrading to the latest version (so that you use AFHTTPSessionManager, a NSURLSession based implementation). The NSURLConnection used in 2.x has been deprecated.

    The AFHTTPSessionManager is, unfortunately, not NSOperation-based. But if you want to enjoy the "send the requests only when the connection is established" functionality, you can either wrap them in asynchronous NSOperation subclass yourself (see AFNetworking 3.0 AFHTTPSessionManager using NSOperation) you can use a background session (see AFNetworking 2.0 and background transfers, while written for AFNetworking 2.x, outlines the essentials of using AFHTTPSessionManager and background session which still largely applies for version 3).

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Oh I see; that is something I should have seen. Clearly, the `isExecuting` value should been the clue. So the idea is; whenever there is no connection, the device doesn't even request but simply waits for reachability to show WIFI/Active. Pretty cool! As far as point 2 is considered — how would could I simply do the following — inside the failureBlock for the particular operation: `{ [operationQueue setSuspended:YES]; [operationQueue addOperation:operation.copy]; [operationQueue setSuspended:NO]; }` Does this look decent? – p0lAris Mar 21 '15 at 21:46
  • No, you don't want operations mussing around with the queue's suspend state. I'm not sure why you did that. If we're doing something simplistic, I can imagine something like the following in the failure block: `if ([operationQueue isSuspended]) { [operationQueue.addOperation:[operation copy]]; }`. This is going to screw up the queue (i.e. the thing that should have been at the top of the queue is now at the end, so you might play around with priorities. Also, this is going to lose any dependencies you might of had. You'll have to play around with this a bit, I think. – Rob Mar 21 '15 at 21:53
  • 1
    Frankly, you've got a similar race condition problem here, though, that looking at the queue's `suspended` state is probably not good enough (if you lose the connection mid-request, the op will probably fail before reachability reports the problem. You probably have to look at the `code` and `domain` of the `NSError`, determine if it was cancelled, and write code to handle that. – Rob Mar 21 '15 at 21:57
  • Thanks, currently contemplating if this all could be handled in a much more straightforward manner. – p0lAris Mar 21 '15 at 22:50
  • Pointing to your argument about priorities and position of the operation in the queue — assuming that I have no dependency issue and I create an `AFHTTPRequestOperationManager` for every request, I should be safe? I see this cannot work — as we are dealing with a shared reachability manager. But I am wondering if we could play around here. – p0lAris Mar 21 '15 at 22:54
  • Why would you create a separate manager for every request? Just use the same one. Was only suggesting that you'd increase the priority of any operations that you rescheduled. – Rob Mar 21 '15 at 23:06
  • That solves one issue but it still doesn't the issue when the operationQueue is not in unsuspended state, i.e. there are other requests that are pending and one of the requests in the middle failed midway. In that case, there would be no way to revive that particular operation – p0lAris Mar 21 '15 at 23:16
  • 1
    Add copy back to the queue with higher priority. Or rewrite AFURLConnectionOperation so that the retry logic is part of the core operation. But creating new request manager only makes things worse, IMHO. – Rob Mar 21 '15 at 23:33
  • Ok sounds like a plan. Will update post when I have some code. Thank you. :) – p0lAris Mar 22 '15 at 00:08
  • @p0lAris Can you please post an updated solution i'm facing the same issue. – Parul Garg May 17 '16 at 09:32
  • @ParulGarg currently don't have access to the code; will check when I reach home. But I would strongly recommend you to try following Rob's answer with the chat below (if needed). – p0lAris May 17 '16 at 17:57
  • @p0lAris I have tried and still facing the same issue. If there is no internet connection operation queue is suspended and when internet comes back it starts executing operations again. But i'm getting the no internet connection error when the internet connection is available after operation queue's start. – Parul Garg May 18 '16 at 11:53
  • @ParulGarg - I just tried pOlAris' code on a device (except that I made sure to suspend the queue before starting reachability and before adding anything to the queue, as I described in point 1, above) and it worked fine. (I started the app in airplane mode, turned off airplane mode, and the request went through successfully.) Having said that, I've updated my answer above and, in particular, points 3 and 4 might be relevant to your question. – Rob May 18 '16 at 17:21