Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MKNetworkKit operation doesn't resume/complete when reachability changes

I'm new to MKNetworkKit, but I've been able to add it to my project and it's perfectly working except when dealing with reachability changes.

Here is the situation:

  1. I disable WiFi and run the app.
  2. Even without reachability, I request (using POST) some data by creating a MKNetworkOperation from my MKNetworkEngine subclass. Right before requesting data, the operation is set as freezable (as per Mugunth Kumar's doc).
  3. After enabling WiFi, checkAndRestoreFrozenOperations in MKNetworkEngine is called and it detects there is one pending operation (the one created without reachability), which tries to enqueue.
  4. After that, my onCompletion block is never called.

Is there anything I don't understand about freezing operations + reachability in MKNetworkKit? Does freeze only work for operations where reachability changes after a request has started? Or should I implement my own reachability changed block?

Here is the code in my MKNetworkEngine subclass that creates the operation and starts the request. Note that irrelevant code has been suppressed.

NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObject:@"value" forKey:@"param"];
MKNetworkOperation *op = [self operationWithPath:MYPATH
                                          params:params
                                      httpMethod:@"POST"];
[op setFreezable:YES];

[op onCompletion:^(MKNetworkOperation *completedOperation) {
   // ...
   // Here is where I process response and send the result to my completion block
   // It's called when WiFi is available, but not called otherwise.
   // ...
} onError:^(NSError *error) {
   // It's called when WiFi is available, but not called otherwise.
    DLog(@"Some error");
}];

[self enqueueOperation:op];

return op;
like image 892
msoler Avatar asked Oct 30 '12 13:10

msoler


1 Answers

These are two separate problems: resume vs. complete.

  1. resume: The Freeze/Unfreeze mechanism only works if the cache is enabled

    You must invoke -useCache in your AppDelegate -didFinishLaunchingWithOptions:

    self.networkEngine = [[MKNetworkEngine alloc] init ...];
    [self.networkEngine useCache]; // <- Add this line
    
  2. complete: The completion callback is not invoked upon network status change (i.e. after Unfreeze)

    Yet if you take action (1.) and place a breakpoint in MKNetworkOperation.m -checkAndRestoreFrozenOperations at line:

    [self enqueueOperation:pendingOperation]
    

    you will find that it is invoked when network connectivity is restored, and that pendingOperation is your pending POST. However, since a new MKNetworkOperation has been instantiated (and by then the completion block may no longer exist) your onCompletion block is never called. One possible workaround would be to use a notification instead of a callback.

  3. complete fix: A more robust approach than (2) that will work across launches is to replace the ^{} block callbacks by NSNotifications. Register your listeners early, like in your AppDelegate. Here are the minimum changes required to make MKNetworkKit notifications savvy:

    3a. Insert notifications constants in MKNetworkOperation.h

    #define MKNetworkOperationCompletionNotification @"MKNetworkOperationCompletionNotification"
    #define MKNetworkOperationErrorNotification @"MKNetworkOperationErrorNotification"
    

    3b. Broadcast a success notification in MKNetworkOperation.m -operationSucceeded (Notice that I use postNotificationOnMainThread so that said notification can be listened to from the main thread and change the UI ; see NSOperation and NSNotificationCenter on the main thread):

    -(void) operationSucceeded {
        NSDictionary * aUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
            self, NSStringFromClass([MKNetworkOperation class]),
            nil];
        NSNotification * notification = [NSNotification notificationWithName:MKNetworkOperationCompletionNotification
            object:nil
            userInfo:aUserInfo];
        [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:notification];
        ...
    

    3c. Broadcast a failure notification in MKNetworkOperation.m -operationFailedWithError

    -(void) operationFailedWithError:(NSError*) error {
        self.error = error;
        NSDictionary * aUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
            self,   NSStringFromClass([MKNetworkOperation class]),
            error,  NSStringFromClass([NSError class]),
            nil];
        NSNotification * notification = [NSNotification notificationWithName:MKNetworkOperationErrorNotification
            object:nil
            userInfo:aUserInfo];
    
        [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:notification];
        ...
    

    3d. Register a rather persistent object, like the AppDelegate, as a listener (Don't forget to unregister):

        // Listen to POST changes
        NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
        [defaultCenter addObserver:self
            selector:@selector(mkNetworkOperationCompletionNotification:)
            name:MKNetworkOperationCompletionNotification
            object:nil];
        [defaultCenter addObserver:self
            selector:@selector(mkNetworkOperationErrorNotification:)
            name:MKNetworkOperationErrorNotification
            object:nil];
    

    3e. Sample code of what the listener could look like:

    - (void)mkNetworkOperationCompletionNotification:(NSNotification*)notification {
        MKNetworkOperation *operation = [[notification userInfo]
            objectForKey:NSStringFromClass([MKNetworkOperation class])];
        NSLog(@"operationSucceeded: %@", [operation responseString]);
    }
    
    - (void)mkNetworkOperationErrorNotification:(NSNotification*)notification {
        NSError * error = [[notification userInfo] objectForKey:NSStringFromClass([NSError class])];
        NSLog(@"operationFailedWithError: %@", [error localizedDescription]);
    }
    

Do this, you're done. X.

(Edited to remove unnecessary suggested changes to MKNetworkOperation.h in previous answer, and added paragraph 3. to show how to overcome the ^{} limitations.)

like image 65
SwiftArchitect Avatar answered Nov 29 '22 04:11

SwiftArchitect