0

I need to log in to Facebook with my app, fetch some data and AFTER this is finished, login to Firebase also. I am using dispatch queues for this but it is not working. My blocks don't wait for each other to finish at all.

Here is my code:

dispatch_queue_t loginQueue = dispatch_queue_create("com.balazsvincze.loginQueue", DISPATCH_QUEUE_SERIAL);


//Log in to Facebook
    dispatch_async(loginQueue, ^{
        FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
        [login logInWithReadPermissions: @[@"public_profile",@"user_friends"] fromViewController:viewController handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {

            if(!error){
                NSLog(@"Logged into Facebook");
                self.facebookUserID = [[FBSDKAccessToken currentAccessToken] userID];

                //Load my profile
                [FBSDKProfile loadCurrentProfileWithCompletion:^(FBSDKProfile *profile, NSError *error){
                    if(!error){
                        self.name = profile.name;
                    }

                    else{
                        NSLog(@"Could not load profile: %@",error);
                        completionResult(NO);
                        return;
                    }

                }];
            }

            else{
                NSLog(@"Could not login: %@",error);
                completionResult(NO);
                return;
            }

        }];
    });

    //Log in to Firebase
    dispatch_async(loginQueue, ^{
        FIRAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:[FBSDKAccessToken currentAccessToken].tokenString];

        [[FIRAuth auth] signInWithCredential:credential  completion:^(FIRUser *user, NSError *error) {

            if(!error){
                NSLog(@"Logged into Firebase");
                self.firebaseUserID = user.uid;
            }

            else{
                NSLog(@"Could not login: %@",error);
                completionResult(NO);
                return;
            }
        }];
    });

Any ideas? Thanks!

Paulw11
  • 108,386
  • 14
  • 159
  • 186
Balázs Vincze
  • 3,550
  • 5
  • 29
  • 60
  • 1
    The Facebook login and the firebase logins are themselves asynchronous, so your async dispatched blocks exit while the asynchronous logins are still running. See my explanation here http://stackoverflow.com/questions/42145427/how-does-a-serial-queue-private-dispatch-queue-know-when-a-task-is-complete/42146095?noredirect=1#comment71500175_42146095 – Paulw11 Feb 10 '17 at 23:56
  • Thanks! I read it but I am afraid that I still don't understand... Please see my edited answer! – Balázs Vincze Feb 11 '17 at 00:08
  • You don't want the first wait; you only want a wait when you want to block pending completion of the previous task. You still need your dispatch_async or you will block the main thread on the wait. you could also use a dispatch_group_notify since the two operations are independent and you just want to know when both have completed. – Paulw11 Feb 11 '17 at 00:11
  • Okay, so I tried it with dispatch_group_notify, however they still don't wait for each other... What am I doing wrong? – Balázs Vincze Feb 11 '17 at 00:14
  • You need to put the dispatch_group_notify *after* the other asynchronous blocks. I will revert your question, since your changed code is worse and add an answer – Paulw11 Feb 11 '17 at 00:19
  • OMG, those changes completely the question! Thanks, Paulw11 for reverting. – Rob Feb 11 '17 at 00:21
  • Oh I overlooked that, sorry! Thanks for reverting. – Balázs Vincze Feb 11 '17 at 00:22
  • So, I return to my original comment that you should either (a) just nest them like Russell suggests; (b) nest them but put the subsequent calls in separate methods, avoiding the ugly nested blocks situation; or (c) use solutions like asynchronous `NSOperation` custom subclasses or promises ([PromiseKit](http://promisekit.org) or [RXPromise](https://github.com/couchdeveloper/RXPromise)), though those are probably over-engineered if this is just a one-time issue. – Rob Feb 11 '17 at 00:25
  • Thanks for the suggestions @Rob! All of them are asynchronous calls so would putting them into separate functions mean that those separate functions would need completion blocks? – Balázs Vincze Feb 11 '17 at 00:27
  • Yep, if you want to pass some completion handler that the final function will call when it's done, you can do that. – Rob Feb 11 '17 at 00:37

2 Answers2

2

make the call to login in Firebase within the completion handler for Facebook, and it will all work as expected

Russell
  • 5,436
  • 2
  • 19
  • 27
1

Although you are dispatching asynchronous tasks on a serial dispatch queue, those tasks themselves dispatch asynchronous tasks and then end; so your tasks have completed but the logins haven't.

As @Rob suggested, splitting your code into separate tasks using something like NSOperation will result in cleaner, more scalable code.

However, here is how you can use a dispatch_group and dispatch_group_notify to schedule some code when the asynchronous operations have completed:

dispatch_group_t dispatchGroup = dispatch_group_create();

//Log in to Facebook
dispatch_group_enter(dispatchGroup);

FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
[login logInWithReadPermissions: @[@"public_profile",@"user_friends"] fromViewController:viewController handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {

    if(!error){
        NSLog(@"Logged into Facebook");
        self.facebookUserID = [[FBSDKAccessToken currentAccessToken] userID];

        //Load my profile
        dispatch_group_enter(dispatchGroup);
        [FBSDKProfile loadCurrentProfileWithCompletion:^(FBSDKProfile *profile, NSError *error){
            if(!error){
                self.name = profile.name;
            }
            else{
                NSLog(@"Could not load profile: %@",error);
                completionResult(NO);
            }
            dispatch_group_leave(dispatchGroup);
        }];
    }
    else{
        NSLog(@"Could not login: %@",error);
        completionResult(NO);
    }
    dispatch_group_leave(dispatchGroup);
}];

//Log in to Firebase
dispatch_group_enter(dispatchGroup);

FIRAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:[FBSDKAccessToken currentAccessToken].tokenString];

[[FIRAuth auth] signInWithCredential:credential  completion:^(FIRUser *user, NSError *error) {

    if(!error){
        NSLog(@"Logged into Firebase");
        self.firebaseUserID = user.uid;
    }

    else{
        NSLog(@"Could not login: %@",error);
        completionResult(NO);
    }
    dispatch_group_leave(dispatchGroup);
}];

dispatch_group_notify(dispatchGroup,dispatch_get_main_queue(),^{
    NSLog(@"All login tasks completed");
    // Do whatever
});
Paulw11
  • 108,386
  • 14
  • 159
  • 186