瀏覽代碼

Moves most of FIRAuth initialization out of the calling thread. (#115)

* Adds an option to time initialization of FIRAuth instance in the sample app.

* Changes deployment target from 10.3 to 8.0 for Auth samples.

* Fixes some Xcode warnings in the Auth sample app.

* Moves most of `FIRAuth` initialization to the work thread.

* Fixes a typo.

* Fixes accessing of public `currentUser` method from internal method.

* Fixes tests for macOS.

* Addresses review comments.
Xiangtian Dai 8 年之前
父節點
當前提交
fb5aa974ee

+ 77 - 3
AuthSamples/Sample/MainViewController.m

@@ -20,6 +20,7 @@
 
 #import "FIRAdditionalUserInfo.h"
 #import "FIRApp.h"
+#import "FIRAppAssociationRegistration.h"
 #import "FIROAuthProvider.h"
 #import "FIRPhoneAuthCredential.h"
 #import "FIRPhoneAuthProvider.h"
@@ -362,6 +363,11 @@ static NSString *const kCreateUserTitle = @"Create User";
  */
 static NSString *const kDeleteAppTitle = @"Delete App";
 
+/** @var kTimeAuthInitTitle
+    @brief The text of the "Time Auth Initialization" button.
+ */
+static NSString *const kTimeAuthInitTitle = @"Time Auth Initialization";
+
 /** @var kSectionTitleManualTests
     @brief The section title for automated manual tests.
  */
@@ -493,6 +499,30 @@ typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable e
 - (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(nonnull FIRTokenCallback)callback;
 @end
 
+/** @category FIRAppAssociationRegistration(Deregistration)
+    @brief The category for the deregistration method.
+ */
+@interface FIRAppAssociationRegistration (Deregistration)
+/** @fn deregisteredObjectWithHost:key:
+    @brief Removes the object that was registered with a particular host and key, if one exists.
+    @param host The host object.
+    @param key The key to specify the registered object on the host.
+ */
++ (void)deregisterObjectWithHost:(id)host key:(NSString *)key;
+@end
+
+@implementation FIRAppAssociationRegistration (Deregistration)
+
++ (void)deregisterObjectWithHost:(id)host key:(NSString *)key {
+  @synchronized(self) {
+    SEL dictKey = @selector(registeredObjectWithHost:key:creationBlock:);
+    NSMutableDictionary<NSString *, id> *objectsByKey = objc_getAssociatedObject(host, dictKey);
+    [objectsByKey removeObjectForKey:key];
+  }
+}
+
+@end
+
 @implementation MainViewController {
   NSMutableString *_consoleString;
 
@@ -681,7 +711,9 @@ typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable e
         [StaticContentTableViewCell cellWithTitle:kTokenGetButtonText
                                            action:^{ [weakSelf getAppTokenWithForce:NO]; }],
         [StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText
-                                           action:^{ [weakSelf getAppTokenWithForce:YES]; }]
+                                           action:^{ [weakSelf getAppTokenWithForce:YES]; }],
+        [StaticContentTableViewCell cellWithTitle:kTimeAuthInitTitle
+                                           action:^{ [weakSelf timeAuthInitialization]; }]
       ]],
       [StaticContentTableViewSection sectionWithTitle:kSectionTitleListeners cells:@[
         [StaticContentTableViewCell cellWithTitle:kAddAuthStateListenerTitle
@@ -730,8 +762,8 @@ typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable e
 
 /** @fn signInWithProvider:provider:
     @brief Perform sign in with credential operataion, for given auth provider.
-    @provider The auth provider.
-    @callback The callback to continue the flow which executed this sign-in.
+    @param provider The auth provider.
+    @param callback The callback to continue the flow which executed this sign-in.
  */
 - (void)signInWithProvider:(nonnull id<AuthProvider>)provider callback:(void(^)(void))callback {
   if (!provider) {
@@ -2382,6 +2414,48 @@ typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable e
   }];
 }
 
+/** @fn timeAuthInitialization
+    @brief Times FIRAuth instance initialization time.
+ */
+- (void)timeAuthInitialization {
+  // Temporarily disable auth state listener to avoid interfering with the result.
+  [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                  name:FIRAuthStateDidChangeNotification
+                                                object:nil];
+  [self showSpinner:^() {
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^() {
+      const int numberOfRuns = 4096;
+      FIRApp *app = [FIRApp defaultApp];
+      NSString *key = NSStringFromClass([FIRAuth class]);
+      NSDate *startTime = [NSDate date];
+      for (int i = 0; i < numberOfRuns; i++) {
+        @autoreleasepool {
+          [FIRAppAssociationRegistration deregisterObjectWithHost:app key:key];
+          [FIRAuth auth];
+        }
+      }
+      NSDate *endTime = [NSDate date];
+      dispatch_async(dispatch_get_main_queue(), ^() {
+        // Re-enable auth state listener.
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(authStateChangedForAuth:)
+                                                     name:FIRAuthStateDidChangeNotification
+                                                   object:nil];
+        [self hideSpinner:^() {
+          NSTimeInterval averageTime = [endTime timeIntervalSinceDate:startTime] / numberOfRuns;
+          NSString *message = [NSString stringWithFormat:
+              @"Each [FIRAuth auth] takes average of %.3f ms for %d runs",
+              averageTime * 1000, numberOfRuns];
+          [self showMessagePromptWithTitle:@"Timing Result"
+                                   message:message
+                          showCancelButton:NO
+                                completion:nil];
+        }];
+      });
+    });
+  }];
+}
+
 #pragma mark - Helpers
 
 /** @fn user

+ 0 - 2
AuthSamples/Sample/StaticContentTableViewManager.h

@@ -242,8 +242,6 @@ typedef void(^StaticContentTableViewCellAction)(void);
     @param customCell The custom @c UITableViewCell to use for this cell.
     @param title If no custom cell is being used, this is the text of the @c titleLabel of the
         @c UITableViewCell.
-    @param title If no custom cell is being used, this is the text of the @c detailTextLabel of the
-        @c UITableViewCell.
     @param action A block which is executed when the cell is selected.
     @param accessibilityID The accessibility ID to add to the cell.
  */

+ 1 - 1
AuthSamples/Sample/UIViewController+Alerts.h

@@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
  */
 typedef void (^AlertPromptCompletionBlock)(BOOL userPressedOK, NSString *_Nullable userInput);
 
-/*! @class Alerts
+/*! @category UIViewController(Alerts)
     @brief Wrapper for @c UIAlertController and @c UIAlertView for backwards compatability with
         iOS 6+.
  */

+ 2 - 2
AuthSamples/Samples.xcodeproj/project.pbxproj

@@ -1399,7 +1399,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 10.3;
+				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
@@ -1442,7 +1442,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 10.3;
+				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";

+ 57 - 31
Firebase/Auth/Source/FIRAuth.m

@@ -199,6 +199,11 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
 @end
 
 @implementation FIRAuth {
+  /** @var _currentUser
+      @brief The current user.
+   */
+  FIRUser *_currentUser;
+
   /** @var _firebaseAppName
       @brief The Firebase app name.
    */
@@ -315,6 +320,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
     app.getTokenImplementation = ^(BOOL forceRefresh, FIRTokenCallback callback) {
       dispatch_async(FIRAuthGlobalWorkQueue(), ^{
         FIRAuth *strongSelf = weakSelf;
+        // Enable token auto-refresh if not aleady enabled.
         if (strongSelf && !strongSelf->_autoRefreshTokens) {
           FIRLogInfo(kFIRLoggerAuth, @"I-AUT000002", @"Token auto-refresh enabled.");
           strongSelf->_autoRefreshTokens = YES;
@@ -346,15 +352,17 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
           }];
           #endif
         }
-        if (!strongSelf.currentUser) {
+        // Call back with 'nil' if there is no current user.
+        if (!strongSelf || !strongSelf->_currentUser) {
           dispatch_async(dispatch_get_main_queue(), ^{
             callback(nil, nil);
           });
           return;
         }
-        [strongSelf.currentUser internalGetTokenForcingRefresh:forceRefresh
-                                                      callback:^(NSString *_Nullable token,
-                                                                 NSError *_Nullable error) {
+        // Call back with current user token.
+        [strongSelf->_currentUser internalGetTokenForcingRefresh:forceRefresh
+                                                        callback:^(NSString *_Nullable token,
+                                                                   NSError *_Nullable error) {
           dispatch_async(dispatch_get_main_queue(), ^{
             callback(token, error);
           });
@@ -378,33 +386,47 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
     _listenerHandles = [NSMutableArray array];
     _APIKey = [APIKey copy];
     _firebaseAppName = [appName copy];
-    NSString *keychainServiceName = [FIRAuth keychainServiceNameForAppName:appName];
-    if (keychainServiceName) {
-      _keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName];
-    }
-    // Load current user from keychain.
-    FIRUser *user;
-    NSError *error;
-    if ([self getUser:&user error:&error]) {
-      [self updateCurrentUser:user byForce:NO savingToDisk:NO error:&error];
-    } else {
-      FIRLogError(kFIRLoggerAuth, @"I-AUT000001",
-                  @"Error loading saved user when starting up: %@", error);
-    }
-
     #if TARGET_OS_IOS
-    // Initialize for phone number auth.
-    _tokenManager =
-        [[FIRAuthAPNSTokenManager alloc] initWithApplication:[UIApplication sharedApplication]];
+    UIApplication *application = [UIApplication sharedApplication];
+    #endif
+
+    // Continue with the rest of initialization in the work thread.
+    __weak FIRAuth *weakSelf = self;
+    dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+      // Load current user from Keychain.
+      FIRAuth *strongSelf = weakSelf;
+      if (!strongSelf) {
+        return;
+      }
+      NSString *keychainServiceName =
+          [FIRAuth keychainServiceNameForAppName:strongSelf->_firebaseAppName];
+      if (keychainServiceName) {
+        strongSelf->_keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName];
+      }
+      FIRUser *user;
+      NSError *error;
+      if ([strongSelf getUser:&user error:&error]) {
+        [strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error];
+      } else {
+        FIRLogError(kFIRLoggerAuth, @"I-AUT000001",
+                    @"Error loading saved user when starting up: %@", error);
+      }
 
-    _appCredentialManager = [[FIRAuthAppCredentialManager alloc] initWithKeychain:_keychain];
+      #if TARGET_OS_IOS
+      // Initialize for phone number auth.
+      strongSelf->_tokenManager =
+          [[FIRAuthAPNSTokenManager alloc] initWithApplication:application];
 
-    _notificationManager =
-        [[FIRAuthNotificationManager alloc] initWithApplication:[UIApplication sharedApplication]
-                                           appCredentialManager:_appCredentialManager];
+      strongSelf->_appCredentialManager =
+          [[FIRAuthAppCredentialManager alloc] initWithKeychain:strongSelf->_keychain];
 
-    [[FIRAuthAppDelegateProxy sharedInstance] addHandler:self];
-    #endif
+      strongSelf->_notificationManager = [[FIRAuthNotificationManager alloc]
+           initWithApplication:application
+          appCredentialManager:strongSelf->_appCredentialManager];
+
+      [[FIRAuthAppDelegateProxy sharedInstance] addHandler:strongSelf];
+      #endif
+    });
   }
   return self;
 }
@@ -431,6 +453,14 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
 
 #pragma mark - Public API
 
+- (FIRUser *)currentUser {
+  __block FIRUser *result;
+  dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
+    result = _currentUser;
+  });
+  return result;
+}
+
 - (void)fetchProvidersForEmail:(NSString *)email
                     completion:(FIRProviderQueryCallback)completion {
   dispatch_async(FIRAuthGlobalWorkQueue(), ^{
@@ -1275,10 +1305,6 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
   return YES;
 }
 
-/** @fn getUID
-    @brief Gets the identifier of the current user, if any.
-    @return The identifier of the current user, or nil if there is no current user.
- */
 - (nullable NSString *)getUID {
   return _currentUser.uid;
 }

+ 6 - 0
Firebase/Auth/Source/FIRAuth_Internal.h

@@ -75,6 +75,12 @@ extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey;
 - (nullable instancetype)initWithAPIKey:(NSString *)APIKey
                                 appName:(NSString *)appName NS_DESIGNATED_INITIALIZER;
 
+/** @fn getUID
+    @brief Gets the identifier of the current user, if any.
+    @return The identifier of the current user, or nil if there is no current user.
+ */
+- (nullable NSString *)getUID;
+
 /** @fn notifyListenersOfAuthStateChange
     @brief Posts the @c FIRAuthStateDidChangeNotification notification.
     @remarks Called by @c FIRUser when token changes occur.

+ 1 - 1
Firebase/Auth/Source/FIRUser.m

@@ -734,7 +734,7 @@ static void callInMainThreadWithAuthDataResultAndError(
         callInMainThreadWithAuthDataResultAndError(completion, authResult, error);
         return;
       }
-      if (![authResult.user.uid isEqual:_auth.currentUser.uid]) {
+      if (![authResult.user.uid isEqual:[_auth getUID]]) {
         callInMainThreadWithAuthDataResultAndError(completion, authResult,
                                                    [FIRAuthErrorUtils userMismatchError]);
         return;