4

Does anyone know the best practice that how to start another new asynchronous method in the completion block of the first asynchronous communication?

I am testing the code to make a call NSFetchRequest(coz STACKMOB iOS SDK internally sync with server) asynchronously in completion callback of another asynchronous communication to Facebook. The execution of code suddenly terminates at the line of NSFetchRequest. I realized one of the reason why it doesn't work correctly. I guess that the completion block has been released from memory as soon as [managedObjectContext executeFetchRequest:fetchRequest error:&error] is invoked. but I don't know better solution to fix it. Thanks for any help.

The SDK uses:

  • (void)queueRequest:(NSURLRequest *)request options:(SMRequestOptions *)options onSuccess:(SMFullResponseSuccessBlock)onSuccess onFailure:(SMFullResponseFailureBlock)onFailure

https://github.com/stackmob/stackmob-ios-sdk/blob/master/Classes/SMDataStore%2BProtected.m

I tried:

:

- (IBAction)checkFacebookInfo:(id)sender
{
    //completion block of facebook info
    void(^onCompleteBlock)(NSDictionary*) = [[^(NSDictionary* userInfo)
    {
        NSManagedObjectContext *managedObjectContext = nil;
        managedObjectContext = [[SingletonCoreData sharedManager] managedObjectContext];

        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"User"];

        //for STACKMOB, customized NSFetchRequest internally sync to the server. It is Asynchronous method.
        NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];// failed

        //Not reached here
        //set userInfo to results here

    } copy] autorelease];

    //invoke onCompleteBlock after executing asynchronously, client(SMClient object for STACKMOB)
    [client getLoggedInUserFacebookInfoWithOnSuccess:onCompleteBlock onFailure:^(NSError *error)
     {

         NSLog(@"No user found");

     }];

}

Edited: I tried this written below, then it successfully works. But I feel it slow. I put a part of the code into 'dispatch_async' block. I am waiting for any other better solution.

    - (IBAction)checkFacebookInfo:(id)sender
    {
        //completion block of facebook info
        void(^onCompleteBlock)(NSDictionary*) = ^(NSDictionary* userInfo)
        {

            dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

            dispatch_async(gQueue, ^{
                NSManagedObjectContext *managedObjectContext = nil;
                managedObjectContext = [[SingletonCoreData sharedManager] managedObjectContext];

                NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"User"];

                //for STACKMOB, customized NSFetchRequest internally sync to the server. It is Asynchronous method.
                NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];// success

                //set userInfo to results here

            });

        };

        //invoke onCompleteBlock after executing asynchronously, client(SMClient object for STACKMOB)
        [client getLoggedInUserFacebookInfoWithOnSuccess:onCompleteBlock onFailure:^(NSError *error)
         {

             NSLog(@"No user found");

         }];

}
Community
  • 1
  • 1
wheel
  • 41
  • 3
  • Have you tried calling your fetcheRequest with retain after alloc/init and then after using it calling release? – Yuliani Noriega Nov 20 '12 at 13:08
  • Thanks yulz, I have tried it but it does not work for me. – wheel Nov 20 '12 at 13:44
  • First of all, you don't need to copy and autorelease the block. "the problem is probably that it releases the caller block from memory before the NSFetchRequest finishes" What? This statement doesn't make any sense – user102008 Nov 20 '12 at 19:07

1 Answers1

0

If you're attempting to synchronize multiple asynchronous operations (over the network or otherwise), I can't recommend enough using promises. For a general introduction to promises, a recent blog entry from Los Techies is pretty good:

http://lostechies.com/derickbailey/2012/07/19/want-to-build-win8winjs-apps-you-need-to-understand-promises/

That said, there's a good library called KSPromise that I've used quite a bit on iOS: https://github.com/kseebaldt/deferred

Using promises, you could make your fetch request after one or more asynchronous calls complete. For example, a join promise will only be resolved once all of its dependent promises have also resolved! I think you'll find that using promises your code becomes more organized and easier to read, especially if you're doing something complex.

That said, here's a fairly contrived example that shows a possible way to write your code using promises (written vs. the Facebook iOS 3.1 SDK):

- (IBAction)checkFacebookInfo:(FBSession *)session {
  KSDeferred *dfd = [KSDeferred defer];

  FBRequest *meRequest = [[FBRequest alloc] initWithSession:session graphPath:@"/me"];
  [meRequest startWithCompletionHandler:
    ^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
      if (error) {
        [dfd.promise rejectWithError:error];
      } else {
        [dfd.promise resolveWithValue:user];
      }
  }];

  [dfd.promise whenResolved:^(KSPromise *p) {
    NSDictionary *userInfo = p.value;
    NSManagedObjectContext *managedObjectContext = [[SingletonCoreData sharedManager] managedObjectContext];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"User"];
    NSError *error;
    NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    // Do whatever with results and userInfo here
  }];

  [dfd.promise whenRejected:^(KSPromise *p) {
    NSError *e = (NSError *)p.value;
    NSLog(@"Error: %@", [e localizedDescription]);
  }];
}

One last thing: be very, very careful when using Core Data with threads and dispatch_async. You can run into all sorts of hard-to-debug concurrency issues.