6

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;
msoler
  • 2,930
  • 2
  • 18
  • 30
  • Did you set up a host in the engine? If you look at how frozen operations are dealt with, they don't work unless there's a host defined for the engine. – Ken Woo Nov 19 '12 at 01:50
  • @KenWoo Yes. The init method in my engine subclass calls `[super initWithHostName:]`. – msoler Nov 20 '12 at 23:13
  • @msoler Did you get it to work? I have a similar problem – alandalusi Dec 19 '12 at 17:06
  • @alandalusi No, sorry. I've been busy with several parts of the project and almost forgot about this. I tried to contact the developer of MKNetworkKit but I got no response. – msoler Dec 27 '12 at 20:14
  • 1
    If you go through the checkAndRestoreFrozenOperations function, it is creating new MKOperation so in no way your block is going to be called. But the thing is the post data is not uploaded to the server in this scenario. – JOA80 Feb 19 '13 at 07:27
  • Correct. The new operation will not invoke the completion block, which may be long gone. But this question is about caching a "POST", which does not work unless useCache is turned on. – SwiftArchitect Apr 25 '13 at 19:29

1 Answers1

6

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.)

Community
  • 1
  • 1
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179