Explorar el Código

experimental auth custom callback queue feature

Morgan Chen hace 4 años
padre
commit
8d07d9fd95

+ 31 - 22
FirebaseAuth/Sources/Auth/FIRAuth.m

@@ -465,6 +465,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
 - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)appName {
   self = [super init];
   if (self) {
+    _callbackQueue = dispatch_get_main_queue();
     _listenerHandles = [NSMutableArray array];
     _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey];
     _firebaseAppName = [appName copy];
@@ -581,6 +582,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   return result;
 }
 
+- (void)setCallbackQueue:(dispatch_queue_t _Nullable)callbackQueue {
+  if (callbackQueue == nil) {
+    _callbackQueue = dispatch_get_main_queue();
+  } else {
+    _callbackQueue = callbackQueue;
+  }
+}
+
 - (void)signInWithProvider:(id<FIRFederatedAuthProvider>)provider
                 UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
                 completion:(nullable FIRAuthDataResultCallback)completion {
@@ -615,7 +624,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
         createAuthURI:request
              callback:^(FIRCreateAuthURIResponse *_Nullable response, NSError *_Nullable error) {
                if (completion) {
-                 dispatch_async(dispatch_get_main_queue(), ^{
+                 dispatch_async(self.callbackQueue, ^{
                    completion(response.signinMethods, error);
                  });
                }
@@ -1128,7 +1137,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
         resetPassword:request
              callback:^(FIRResetPasswordResponse *_Nullable response, NSError *_Nullable error) {
                if (completion) {
-                 dispatch_async(dispatch_get_main_queue(), ^{
+                 dispatch_async(self.callbackQueue, ^{
                    if (error) {
                      completion(error);
                      return;
@@ -1151,7 +1160,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
              callback:^(FIRResetPasswordResponse *_Nullable response, NSError *_Nullable error) {
                if (completion) {
                  if (error) {
-                   dispatch_async(dispatch_get_main_queue(), ^{
+                   dispatch_async(self.callbackQueue, ^{
                      completion(nil, error);
                    });
                    return;
@@ -1162,7 +1171,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
                      [[FIRActionCodeInfo alloc] initWithOperation:operation
                                                             email:response.email
                                                          newEmail:response.verifiedEmail];
-                 dispatch_async(dispatch_get_main_queue(), ^{
+                 dispatch_async(self.callbackQueue, ^{
                    completion(actionCodeInfo, nil);
                  });
                }
@@ -1193,7 +1202,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
         setAccountInfo:request
               callback:^(FIRSetAccountInfoResponse *_Nullable response, NSError *_Nullable error) {
                 if (completion) {
-                  dispatch_async(dispatch_get_main_queue(), ^{
+                  dispatch_async(self.callbackQueue, ^{
                     completion(error);
                   });
                 }
@@ -1242,7 +1251,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
                                   callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
                                              NSError *_Nullable error) {
                                     if (completion) {
-                                      dispatch_async(dispatch_get_main_queue(), ^{
+                                      dispatch_async(self.callbackQueue, ^{
                                         completion(error);
                                       });
                                     }
@@ -1271,7 +1280,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
                                   callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
                                              NSError *_Nullable error) {
                                     if (completion) {
-                                      dispatch_async(dispatch_get_main_queue(), ^{
+                                      dispatch_async(self.callbackQueue, ^{
                                         completion(error);
                                       });
                                     }
@@ -1283,7 +1292,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     if (!user) {
       if (completion) {
-        dispatch_async(dispatch_get_main_queue(), ^{
+        dispatch_async(self.callbackQueue, ^{
           completion([FIRAuthErrorUtils nullUserErrorWithMessage:nil]);
         });
       }
@@ -1294,14 +1303,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
       [self updateCurrentUser:user byForce:YES savingToDisk:YES error:(&error)];
       if (error) {
         if (completion) {
-          dispatch_async(dispatch_get_main_queue(), ^{
+          dispatch_async(self.callbackQueue, ^{
             completion(error);
           });
         }
         return;
       }
       if (completion) {
-        dispatch_async(dispatch_get_main_queue(), ^{
+        dispatch_async(self.callbackQueue, ^{
           completion(nil);
         });
       }
@@ -1313,7 +1322,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
       [user reloadWithCompletion:^(NSError *_Nullable error) {
         if (error) {
           if (completion) {
-            dispatch_async(dispatch_get_main_queue(), ^{
+            dispatch_async(self.callbackQueue, ^{
               completion(error);
             });
           }
@@ -1408,7 +1417,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   @synchronized(self) {
     [_listenerHandles addObject:handle];
   }
-  dispatch_async(dispatch_get_main_queue(), ^{
+  dispatch_async(self.callbackQueue, ^{
     listener(self, self->_currentUser);
   });
   return handle;
@@ -1712,7 +1721,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] =
       _currentUser.uid;
   NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter];
-  dispatch_async(dispatch_get_main_queue(), ^{
+  dispatch_async(self.callbackQueue, ^{
     [notifications postNotificationName:FIRAuthStateDidChangeInternalNotification
                                  object:self
                                userInfo:internalNotificationParameters];
@@ -1883,7 +1892,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   return ^(FIRUser *_Nullable user, NSError *_Nullable error) {
     if (error) {
       if (callback) {
-        dispatch_async(dispatch_get_main_queue(), ^{
+        dispatch_async(self.callbackQueue, ^{
           callback(nil, error);
         });
       }
@@ -1891,14 +1900,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
     }
     if (![self updateCurrentUser:user byForce:NO savingToDisk:YES error:&error]) {
       if (callback) {
-        dispatch_async(dispatch_get_main_queue(), ^{
+        dispatch_async(self.callbackQueue, ^{
           callback(nil, error);
         });
       }
       return;
     }
     if (callback) {
-      dispatch_async(dispatch_get_main_queue(), ^{
+      dispatch_async(self.callbackQueue, ^{
         callback(user, nil);
       });
     }
@@ -1920,7 +1929,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   return ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
     if (error) {
       if (callback) {
-        dispatch_async(dispatch_get_main_queue(), ^{
+        dispatch_async(self.callbackQueue, ^{
           callback(nil, error);
         });
       }
@@ -1928,14 +1937,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
     }
     if (![self updateCurrentUser:authResult.user byForce:NO savingToDisk:YES error:&error]) {
       if (callback) {
-        dispatch_async(dispatch_get_main_queue(), ^{
+        dispatch_async(self.callbackQueue, ^{
           callback(nil, error);
         });
       }
       return;
     }
     if (callback) {
-      dispatch_async(dispatch_get_main_queue(), ^{
+      dispatch_async(self.callbackQueue, ^{
         callback(authResult, nil);
       });
     }
@@ -2127,7 +2136,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
       NSString *userKey = [NSString stringWithFormat:kUserKey, app.name];
       [keychain removeDataForKey:userKey error:NULL];
     }
-    dispatch_async(dispatch_get_main_queue(), ^{
+    dispatch_async(self.callbackQueue, ^{
       // TODO: Move over to fire an event instead, once ready.
       [[NSNotificationCenter defaultCenter] postNotificationName:FIRAuthStateDidChangeNotification
                                                           object:nil];
@@ -2175,7 +2184,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
     }
     // Call back with 'nil' if there is no current user.
     if (!strongSelf || !strongSelf->_currentUser) {
-      dispatch_async(dispatch_get_main_queue(), ^{
+      dispatch_async(self.callbackQueue, ^{
         callback(nil, nil);
       });
       return;
@@ -2184,7 +2193,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
     [strongSelf->_currentUser
         internalGetTokenForcingRefresh:forceRefresh
                               callback:^(NSString *_Nullable token, NSError *_Nullable error) {
-                                dispatch_async(dispatch_get_main_queue(), ^{
+                                dispatch_async(self.callbackQueue, ^{
                                   callback(token, error);
                                 });
                               }];

+ 7 - 6
FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthProvider.m

@@ -143,11 +143,12 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
   __weak __typeof__(self) weakSelf = self;
   __weak FIRAuth *weakAuth = _auth;
   __weak NSString *weakProviderID = _providerID;
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
-    FIRAuthCredentialCallback callbackOnMainThread =
+    FIRAuthCredentialCallback invokeCompletion =
         ^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) {
           if (completion) {
-            dispatch_async(dispatch_get_main_queue(), ^{
+            dispatch_async(callbackQueue, ^{
               completion(credential, error);
             });
           }
@@ -160,7 +161,7 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
                            sessionID:sessionID
                           completion:^(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error) {
                             if (error) {
-                              callbackOnMainThread(nil, error);
+                              invokeCompletion(nil, error);
                               return;
                             }
                             FIRAuthURLCallbackMatcher callbackMatcher =
@@ -179,14 +180,14 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
                                      completion:^(NSURL *_Nullable callbackURL,
                                                   NSError *_Nullable error) {
                                        if (error) {
-                                         callbackOnMainThread(nil, error);
+                                         invokeCompletion(nil, error);
                                          return;
                                        }
                                        NSString *OAuthResponseURLString =
                                            [strongSelf OAuthResponseForURL:callbackURL
                                                                      error:&error];
                                        if (error) {
-                                         callbackOnMainThread(nil, error);
+                                         invokeCompletion(nil, error);
                                          return;
                                        }
                                        __strong NSString *strongProviderID = weakProviderID;
@@ -194,7 +195,7 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
                                                initWithProviderID:strongProviderID
                                                         sessionID:sessionID
                                            OAuthResponseURLString:OAuthResponseURLString];
-                                       callbackOnMainThread(credential, nil);
+                                       invokeCompletion(credential, nil);
                                      }];
                           }];
   });

+ 10 - 8
FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthProvider.m

@@ -148,11 +148,12 @@ extern NSString *const FIRPhoneMultiFactorID;
                 format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
                        _callbackScheme];
   }
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
-    FIRVerificationResultCallback callBackOnMainThread =
+    FIRVerificationResultCallback invokeCompletion =
         ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
           if (completion) {
-            dispatch_async(dispatch_get_main_queue(), ^{
+            dispatch_async(callbackQueue, ^{
               completion(verificationID, error);
             });
           }
@@ -162,10 +163,10 @@ extern NSString *const FIRPhoneMultiFactorID;
                        UIDelegate:UIDelegate
                        completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
                          if (!error) {
-                           callBackOnMainThread(verificationID, nil);
+                           invokeCompletion(verificationID, nil);
                            return;
                          } else {
-                           callBackOnMainThread(nil, error);
+                           invokeCompletion(nil, error);
                            return;
                          }
                        }];
@@ -197,11 +198,12 @@ extern NSString *const FIRPhoneMultiFactorID;
                 format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
                        _callbackScheme];
   }
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
-    FIRVerificationResultCallback callBackOnMainThread =
+    FIRVerificationResultCallback invokeCompletion =
         ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
           if (completion) {
-            dispatch_async(dispatch_get_main_queue(), ^{
+            dispatch_async(callbackQueue, ^{
               completion(verificationID, error);
             });
           }
@@ -212,10 +214,10 @@ extern NSString *const FIRPhoneMultiFactorID;
                multiFactorSession:session
                        completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
                          if (!error) {
-                           callBackOnMainThread(verificationID, nil);
+                           invokeCompletion(verificationID, nil);
                            return;
                          } else {
-                           callBackOnMainThread(nil, error);
+                           invokeCompletion(nil, error);
                            return;
                          }
                        }];

+ 6 - 0
FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h

@@ -372,6 +372,12 @@ NS_SWIFT_NAME(Auth)
 @property(nonatomic, strong, nullable) NSData *APNSToken;
 #endif
 
+/** @property callbackQueue
+    @brief The queue that Auth callbacks should execute on. By default, this is the main queue. Only set this value before calling any other Auth methods.
+    @remarks The provided queue must be a serial queue. Changing this value will also affect the queue that Auth notifications are delivered on.
+ */
+@property(nonatomic, strong, null_resettable) dispatch_queue_t callbackQueue;
+
 /** @fn init
     @brief Please access auth instances using `FIRAuth.auth` and `FIRAuth.authForApp:`.
  */

+ 1 - 0
FirebaseAuth/Sources/SystemService/FIRAuthAPNSTokenManager.m

@@ -73,6 +73,7 @@ static const NSTimeInterval kLegacyRegistrationTimeout = 30;
   }
   _pendingCallbacks =
       [[NSMutableArray<FIRAuthAPNSTokenCallback> alloc] initWithObjects:callback, nil];
+  // Must be called on the main queue since UIApplication is not thread-safe.
   dispatch_async(dispatch_get_main_queue(), ^{
     if ([self->_application respondsToSelector:@selector(registerForRemoteNotifications)]) {
       [self->_application registerForRemoteNotifications];

+ 1 - 0
FirebaseAuth/Sources/SystemService/FIRAuthNotificationManager.m

@@ -102,6 +102,7 @@ static const NSTimeInterval kProbingTimeout = 1;
   _hasCheckedNotificationForwarding = YES;
   _pendingCallbacks =
       [[NSMutableArray<FIRAuthNotificationForwardingCallback> alloc] initWithObjects:callback, nil];
+  // Must be dispatched to the main queue since UIApplication isn't thread-safe.
   dispatch_async(dispatch_get_main_queue(), ^{
     NSDictionary *proberNotification = @{
       kNotificationDataKey : @{

+ 87 - 68
FirebaseAuth/Sources/User/FIRUser.m

@@ -162,31 +162,35 @@ typedef void (^CallbackWithAuthDataResultAndError)(FIRAuthDataResult *_Nullable,
  */
 static NSString *const kMissingPasswordReason = @"Missing Password";
 
-/** @fn callInMainThreadWithError
+/** @fn callInThreadWithError
     @brief Calls a callback in main thread with error.
     @param callback The callback to be called in main thread.
+    @param thread The thread to execute the callback on.
     @param error The error to pass to callback.
  */
-static void callInMainThreadWithError(_Nullable CallbackWithError callback,
-                                      NSError *_Nullable error) {
+static void callInThreadWithError(_Nullable CallbackWithError callback,
+                                  dispatch_queue_t thread,
+                                  NSError *_Nullable error) {
   if (callback) {
-    dispatch_async(dispatch_get_main_queue(), ^{
+    dispatch_async(thread, ^{
       callback(error);
     });
   }
 }
 
-/** @fn callInMainThreadWithUserAndError
+/** @fn callInThreadWithUserAndError
     @brief Calls a callback in main thread with user and error.
     @param callback The callback to be called in main thread.
+    @param thread The thread to execute the callback on.
     @param user The user to pass to callback if there is no error.
     @param error The error to pass to callback.
  */
-static void callInMainThreadWithUserAndError(_Nullable CallbackWithUserAndError callback,
-                                             FIRUser *_Nonnull user,
-                                             NSError *_Nullable error) {
+static void callInThreadWithUserAndError(_Nullable CallbackWithUserAndError callback,
+                                         dispatch_queue_t thread,
+                                         FIRUser *_Nonnull user,
+                                         NSError *_Nullable error) {
   if (callback) {
-    dispatch_async(dispatch_get_main_queue(), ^{
+    dispatch_async(thread, ^{
       callback(error ? nil : user, error);
     });
   }
@@ -195,15 +199,17 @@ static void callInMainThreadWithUserAndError(_Nullable CallbackWithUserAndError
 /** @fn callInMainThreadWithUserAndError
     @brief Calls a callback in main thread with user and error.
     @param callback The callback to be called in main thread.
+    @param thread The thread to execute the callback on.
     @param result The result to pass to callback if there is no error.
     @param error The error to pass to callback.
  */
-static void callInMainThreadWithAuthDataResultAndError(
+static void callInThreadWithAuthDataResultAndError(
     _Nullable CallbackWithAuthDataResultAndError callback,
+    dispatch_queue_t thread,
     FIRAuthDataResult *_Nullable result,
     NSError *_Nullable error) {
   if (callback) {
-    dispatch_async(dispatch_get_main_queue(), ^{
+    dispatch_async(thread, ^{
       callback(result, error);
     });
   }
@@ -663,22 +669,24 @@ static void callInMainThreadWithAuthDataResultAndError(
 }
 
 - (void)updateEmail:(NSString *)email completion:(nullable FIRUserProfileChangeCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self updateEmail:email
              password:nil
              callback:^(NSError *_Nullable error) {
-               callInMainThreadWithError(completion, error);
+               callInThreadWithError(completion, callbackQueue, error);
              }];
   });
 }
 
 - (void)updatePassword:(NSString *)password
             completion:(nullable FIRUserProfileChangeCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self updateEmail:nil
              password:password
              callback:^(NSError *_Nullable error) {
-               callInMainThreadWithError(completion, error);
+               callInThreadWithError(completion, callbackQueue, error);
              }];
   });
 }
@@ -749,11 +757,12 @@ static void callInMainThreadWithAuthDataResultAndError(
 
 - (void)updatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
                          completion:(nullable FIRUserProfileChangeCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self internalUpdateOrLinkPhoneNumberCredential:phoneAuthCredential
                                     isLinkOperation:NO
                                          completion:^(NSError *_Nullable error) {
-                                           callInMainThreadWithError(completion, error);
+                                           callInThreadWithError(completion, callbackQueue, error);
                                          }];
   });
 }
@@ -786,10 +795,11 @@ static void callInMainThreadWithAuthDataResultAndError(
 #pragma mark -
 
 - (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
                                           NSError *_Nullable error) {
-      callInMainThreadWithError(completion, error);
+      callInThreadWithError(completion, callbackQueue, error);
     }];
   });
 }
@@ -798,6 +808,7 @@ static void callInMainThreadWithAuthDataResultAndError(
 
 - (void)reauthenticateWithCredential:(FIRAuthCredential *)credential
                           completion:(nullable FIRAuthDataResultCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self->_auth
         internalSignInAndRetrieveDataWithCredential:credential
@@ -811,24 +822,24 @@ static void callInMainThreadWithAuthDataResultAndError(
                                                if (error.code == FIRAuthErrorCodeUserNotFound) {
                                                  error = [FIRAuthErrorUtils userMismatchError];
                                                }
-                                               callInMainThreadWithAuthDataResultAndError(
-                                                   completion, authResult, error);
+                                               callInThreadWithAuthDataResultAndError(
+                                                   completion, callbackQueue, authResult, error);
                                                return;
                                              }
                                              if (![authResult.user.uid
                                                      isEqual:[self->_auth getUserID]]) {
-                                               callInMainThreadWithAuthDataResultAndError(
-                                                   completion, authResult,
+                                               callInThreadWithAuthDataResultAndError(
+                                                   completion, callbackQueue, authResult,
                                                    [FIRAuthErrorUtils userMismatchError]);
                                                return;
                                              }
                                              // Successful reauthenticate
-                                             [self
-                                                 setTokenService:authResult.user->_tokenService
-                                                        callback:^(NSError *_Nullable error) {
-                                                          callInMainThreadWithAuthDataResultAndError(
-                                                              completion, authResult, error);
-                                                        }];
+                                             [self setTokenService:authResult.user->_tokenService
+                                                          callback:^(NSError *_Nullable error) {
+                                                            callInThreadWithAuthDataResultAndError(
+                                                                completion, callbackQueue,
+                                                                authResult, error);
+                                                          }];
                                            }];
   });
 }
@@ -868,7 +879,7 @@ static void callInMainThreadWithAuthDataResultAndError(
                             completion:^(FIRAuthTokenResult *_Nullable tokenResult,
                                          NSError *_Nullable error) {
                               if (completion) {
-                                dispatch_async(dispatch_get_main_queue(), ^{
+                                dispatch_async(self.auth.callbackQueue, ^{
                                   completion(tokenResult.token, error);
                                 });
                               }
@@ -880,7 +891,7 @@ static void callInMainThreadWithAuthDataResultAndError(
                             completion:^(FIRAuthTokenResult *_Nullable tokenResult,
                                          NSError *_Nullable error) {
                               if (completion) {
-                                dispatch_async(dispatch_get_main_queue(), ^{
+                                dispatch_async(self.auth.callbackQueue, ^{
                                   completion(tokenResult, error);
                                 });
                               }
@@ -901,7 +912,7 @@ static void callInMainThreadWithAuthDataResultAndError(
                                               tokenResult.expirationDate, [NSDate date]);
                                 }
                                 if (completion) {
-                                  dispatch_async(dispatch_get_main_queue(), ^{
+                                  dispatch_async(self.auth.callbackQueue, ^{
                                     completion(tokenResult, error);
                                   });
                                 }
@@ -1037,11 +1048,12 @@ static void callInMainThreadWithAuthDataResultAndError(
                                  actionCodeSettings:
                                      (nullable FIRActionCodeSettings *)actionCodeSettings
                                          completion:(FIRVerifyBeforeUpdateEmailCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self
         internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
           if (error) {
-            callInMainThreadWithError(completion, error);
+            callInThreadWithError(completion, callbackQueue, error);
             return;
           }
           FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
@@ -1055,7 +1067,7 @@ static void callInMainThreadWithAuthDataResultAndError(
               getOOBConfirmationCode:request
                             callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
                                        NSError *_Nullable error) {
-                              callInMainThreadWithError(completion, error);
+                              callInThreadWithError(completion, callbackQueue, error);
                             }];
         }];
   });
@@ -1063,18 +1075,19 @@ static void callInMainThreadWithAuthDataResultAndError(
 
 - (void)linkWithCredential:(FIRAuthCredential *)credential
                 completion:(nullable FIRAuthDataResultCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     if (self->_providerData[credential.provider]) {
-      callInMainThreadWithAuthDataResultAndError(completion, nil,
-                                                 [FIRAuthErrorUtils providerAlreadyLinkedError]);
+      callInThreadWithAuthDataResultAndError(completion, callbackQueue, nil,
+                                             [FIRAuthErrorUtils providerAlreadyLinkedError]);
       return;
     }
     FIRAuthDataResult *result = [[FIRAuthDataResult alloc] initWithUser:self
                                                      additionalUserInfo:nil];
     if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
       if (self->_hasEmailPasswordCredential) {
-        callInMainThreadWithAuthDataResultAndError(completion, nil,
-                                                   [FIRAuthErrorUtils providerAlreadyLinkedError]);
+        callInThreadWithAuthDataResultAndError(completion, callbackQueue, nil,
+                                               [FIRAuthErrorUtils providerAlreadyLinkedError]);
         return;
       }
       FIREmailPasswordAuthCredential *emailPasswordCredential =
@@ -1084,9 +1097,9 @@ static void callInMainThreadWithAuthDataResultAndError(
                  password:emailPasswordCredential.password
                  callback:^(NSError *error) {
                    if (error) {
-                     callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+                     callInThreadWithAuthDataResultAndError(completion, callbackQueue, nil, error);
                    } else {
-                     callInMainThreadWithAuthDataResultAndError(completion, result, nil);
+                     callInThreadWithAuthDataResultAndError(completion, callbackQueue, result, nil);
                    }
                  }];
       } else {
@@ -1111,7 +1124,8 @@ static void callInMainThreadWithAuthDataResultAndError(
                      callback:^(FIREmailLinkSignInResponse *_Nullable response,
                                 NSError *_Nullable error) {
                        if (error) {
-                         callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+                         callInThreadWithAuthDataResultAndError(completion, callbackQueue, nil,
+                                                                error);
                        } else {
                          // Update the new token and refresh user info again.
                          self->_tokenService = [[FIRSecureTokenService alloc]
@@ -1122,7 +1136,8 @@ static void callInMainThreadWithAuthDataResultAndError(
                          [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
                                                               NSError *_Nullable error) {
                            if (error) {
-                             callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+                             callInThreadWithAuthDataResultAndError(completion, callbackQueue, nil,
+                                                                    error);
                              return;
                            }
 
@@ -1136,19 +1151,19 @@ static void callInMainThreadWithAuthDataResultAndError(
                                                 NSError *_Nullable error) {
                                        if (error) {
                                          [self signOutIfTokenIsInvalidWithError:error];
-                                         callInMainThreadWithAuthDataResultAndError(completion, nil,
-                                                                                    error);
+                                         callInThreadWithAuthDataResultAndError(
+                                             completion, callbackQueue, nil, error);
                                          return;
                                        }
                                        self.anonymous = NO;
                                        [self updateWithGetAccountInfoResponse:response];
                                        if (![self updateKeychain:&error]) {
-                                         callInMainThreadWithAuthDataResultAndError(completion, nil,
-                                                                                    error);
+                                         callInThreadWithAuthDataResultAndError(
+                                             completion, callbackQueue, nil, error);
                                          return;
                                        }
-                                       callInMainThreadWithAuthDataResultAndError(completion,
-                                                                                  result, nil);
+                                       callInThreadWithAuthDataResultAndError(
+                                           completion, callbackQueue, result, nil);
                                      }];
                          }];
                        }
@@ -1178,7 +1193,8 @@ static void callInMainThreadWithAuthDataResultAndError(
                         callback:^(FIRSignInWithGameCenterResponse *_Nullable response,
                                    NSError *_Nullable error) {
                           if (error) {
-                            callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+                            callInThreadWithAuthDataResultAndError(completion, callbackQueue, nil,
+                                                                   error);
                           } else {
                             // Update the new token and refresh user info again.
                             self->_tokenService = [[FIRSecureTokenService alloc]
@@ -1189,7 +1205,8 @@ static void callInMainThreadWithAuthDataResultAndError(
                             [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
                                                                  NSError *_Nullable error) {
                               if (error) {
-                                callInMainThreadWithAuthDataResultAndError(completion, nil, error);
+                                callInThreadWithAuthDataResultAndError(completion, callbackQueue,
+                                                                       nil, error);
                                 return;
                               }
 
@@ -1203,19 +1220,19 @@ static void callInMainThreadWithAuthDataResultAndError(
                                                    NSError *_Nullable error) {
                                           if (error) {
                                             [self signOutIfTokenIsInvalidWithError:error];
-                                            callInMainThreadWithAuthDataResultAndError(completion,
-                                                                                       nil, error);
+                                            callInThreadWithAuthDataResultAndError(
+                                                completion, callbackQueue, nil, error);
                                             return;
                                           }
                                           self.anonymous = NO;
                                           [self updateWithGetAccountInfoResponse:response];
                                           if (![self updateKeychain:&error]) {
-                                            callInMainThreadWithAuthDataResultAndError(completion,
-                                                                                       nil, error);
+                                            callInThreadWithAuthDataResultAndError(
+                                                completion, callbackQueue, nil, error);
                                             return;
                                           }
-                                          callInMainThreadWithAuthDataResultAndError(completion,
-                                                                                     result, nil);
+                                          callInThreadWithAuthDataResultAndError(
+                                              completion, callbackQueue, result, nil);
                                         }];
                             }];
                           }
@@ -1231,11 +1248,11 @@ static void callInMainThreadWithAuthDataResultAndError(
                                       isLinkOperation:YES
                                            completion:^(NSError *_Nullable error) {
                                              if (error) {
-                                               callInMainThreadWithAuthDataResultAndError(
-                                                   completion, nil, error);
+                                               callInThreadWithAuthDataResultAndError(
+                                                   completion, callbackQueue, nil, error);
                                              } else {
-                                               callInMainThreadWithAuthDataResultAndError(
-                                                   completion, result, nil);
+                                               callInThreadWithAuthDataResultAndError(
+                                                   completion, callbackQueue, result, nil);
                                              }
                                            }];
       return;
@@ -1246,7 +1263,7 @@ static void callInMainThreadWithAuthDataResultAndError(
       CallbackWithAuthDataResultAndError completeWithError =
           ^(FIRAuthDataResult *result, NSError *error) {
             complete();
-            callInMainThreadWithAuthDataResultAndError(completion, result, error);
+            callInThreadWithAuthDataResultAndError(completion, callbackQueue, result, error);
           };
       [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
                                            NSError *_Nullable error) {
@@ -1335,7 +1352,7 @@ static void callInMainThreadWithAuthDataResultAndError(
   [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
     CallbackWithError completeAndCallbackWithError = ^(NSError *error) {
       complete();
-      callInMainThreadWithUserAndError(completion, self, error);
+      callInThreadWithUserAndError(completion, self.auth.callbackQueue, self, error);
     };
     [self
         internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
@@ -1430,7 +1447,7 @@ static void callInMainThreadWithAuthDataResultAndError(
     [self
         internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
           if (error) {
-            callInMainThreadWithError(completion, error);
+            callInThreadWithError(completion, self.auth.callbackQueue, error);
             return;
           }
           FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
@@ -1443,18 +1460,19 @@ static void callInMainThreadWithAuthDataResultAndError(
                             callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
                                        NSError *_Nullable error) {
                               [self signOutIfTokenIsInvalidWithError:error];
-                              callInMainThreadWithError(completion, error);
+                              callInThreadWithError(completion, self.auth.callbackQueue, error);
                             }];
         }];
   });
 }
 
 - (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
+  dispatch_queue_t callbackQueue = _auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
     [self
         internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
           if (error) {
-            callInMainThreadWithError(completion, error);
+            callInThreadWithError(completion, callbackQueue, error);
             return;
           }
           FIRDeleteAccountRequest *deleteUserRequest =
@@ -1464,15 +1482,15 @@ static void callInMainThreadWithAuthDataResultAndError(
           [FIRAuthBackend deleteAccount:deleteUserRequest
                                callback:^(NSError *_Nullable error) {
                                  if (error) {
-                                   callInMainThreadWithError(completion, error);
+                                   callInThreadWithError(completion, callbackQueue, error);
                                    return;
                                  }
                                  if (![self->_auth signOutByForceWithUserID:self->_userID
                                                                       error:&error]) {
-                                   callInMainThreadWithError(completion, error);
+                                   callInThreadWithError(completion, callbackQueue, error);
                                    return;
                                  }
-                                 callInMainThreadWithError(completion, error);
+                                 callInThreadWithError(completion, callbackQueue, error);
                                }];
         }];
   });
@@ -1576,6 +1594,7 @@ static void callInMainThreadWithAuthDataResultAndError(
 }
 
 - (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
+  dispatch_queue_t callbackQueue = _user.auth.callbackQueue ?: dispatch_get_main_queue();
   dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
     if (self->_consumed) {
       [NSException raise:NSInternalInconsistencyException
@@ -1585,7 +1604,7 @@ static void callInMainThreadWithAuthDataResultAndError(
     self->_consumed = YES;
     // Return fast if there is nothing to update:
     if (![self hasUpdates]) {
-      callInMainThreadWithError(completion, nil);
+      callInThreadWithError(completion, callbackQueue, nil);
       return;
     }
     NSString *displayName = [self->_displayName copy];
@@ -1604,7 +1623,7 @@ static void callInMainThreadWithAuthDataResultAndError(
         }
         callback:^(NSError *_Nullable error) {
           if (error) {
-            callInMainThreadWithError(completion, error);
+            callInThreadWithError(completion, callbackQueue, error);
             return;
           }
           if (displayNameWasSet) {
@@ -1614,10 +1633,10 @@ static void callInMainThreadWithAuthDataResultAndError(
             [self->_user setPhotoURL:photoURL];
           }
           if (![self->_user updateKeychain:&error]) {
-            callInMainThreadWithError(completion, error);
+            callInThreadWithError(completion, callbackQueue, error);
             return;
           }
-          callInMainThreadWithError(completion, nil);
+          callInThreadWithError(completion, callbackQueue, nil);
         }];
   });
 }

+ 3 - 1
FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h

@@ -48,7 +48,9 @@ typedef BOOL (^FIRAuthURLCallbackMatcher)(NSURL *_Nullable callbackURL);
     @param URL The URL to present.
     @param UIDelegate The UI delegate to present view controller.
     @param completion A block to be called either synchronously if the presentation fails to start,
-        or asynchronously in future on an unspecified thread once the presentation finishes.
+        or asynchronously in future on an unspecified thread once the presentation finishes. This
+   completion block always executes on the main thread regardless of the callbackQueue set in
+   FIRAuth.
  */
 - (void)presentURL:(NSURL *)URL
          UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate