Swift4/Xcode9 Firebase谷歌登录:AppDelegate与ViewController代理差异
Great question—this is a common point of confusion when setting up Firebase auth with Google Sign-In, especially since the official docs lean into a centralized approach with AppDelegate. Let’s break down the differences, pros/cons, and whether your instinct to use a ViewController (or standalone class) makes sense.
GIDSignInDelegate How it works
The official docs set AppDelegate as the delegate because it’s the app’s core entry point, handling system-level callbacks like application(_:open:options:)—the method that catches the redirect back to your app after the Google login flow completes. It then implements GIDSignInDelegate methods like sign(_:didSignInFor:withError:) to process the login result.
Pros
- Centralized system callback handling: Since Google Sign-In requires jumping to Safari/Google’s auth screen and back,
AppDelegateis the natural place to catch that redirect. The official setup minimizes configuration steps for new developers. - Global state checks: If you need to verify login status on app launch (e.g., auto-sign-in a returning user),
AppDelegatecan runGIDSignIn.sharedInstance().restorePreviousSignIn()early in the app lifecycle and route the user to the correct screen. - No VC lifecycle risks:
AppDelegatelives for the entire app session, so you don’t have to worry about the delegate being destroyed mid-login (a rare edge case, but still a potential issue with ViewControllers).
Cons
- Violates single responsibility:
AppDelegateis meant for app lifecycle events and system integrations. Piling login logic (plus eventual Facebook/Apple auth, etc.) turns it into a bloated "catch-all" file that’s hard to maintain. - Clunky UI navigation: As you noticed, performing segues or switching ViewControllers from
AppDelegateis awkward. You have to dig through the app’s window hierarchy to find the right view controller, leading to messy code like:if let window = UIApplication.shared.windows.first, let rootNav = window.rootViewController as? UINavigationController { rootNav.pushViewController(MainViewController(), animated: true) } - Hard to test:
AppDelegateis a global singleton, making it difficult to isolate and mock its login logic for unit tests.
GIDSignInDelegate How it works
You can set the delegate to a dedicated LoginViewController (or a standalone AuthManager class) instead. AppDelegate still needs to handle the URL redirect, but it just forwards the request to GIDSignIn:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { return GIDSignIn.sharedInstance().handle(url) }
Then in your LoginViewController, set the delegate in viewDidLoad():
override func viewDidLoad() { super.viewDidLoad() GIDSignIn.sharedInstance().delegate = self // Additional Google Sign-In config }
And implement the GIDSignInDelegate methods directly in the VC (or your AuthManager).
Pros
- Clear separation of concerns: Login logic lives alongside the UI that triggers it, aligning with MVC principles. This keeps your code organized and easy to debug.
- Seamless UI operations: You can directly call
performSegue(withIdentifier: "showMain", sender: self)or show error alerts right from the ViewController—no messy window hierarchy hacks needed. - Scalable and testable: Using a standalone
AuthManagersingleton (instead of a VC) lets you encapsulate all auth logic in one place. You can test this class independently, and adding new login methods (like Apple Sign-In) only requires modifying this file. - Keeps
AppDelegateclean: LetAppDelegatefocus on its core job, not handling user auth flows.
Cons
- Minor extra setup: You have to add the URL redirect forwarding in
AppDelegate, but this is just one line of code. - VC lifecycle caveats: If the user dismisses the
LoginViewControllermid-login (e.g., by pressing back), the delegate becomes nil, and the login result callback is lost. Fix this by using a singletonAuthManageras the delegate instead—its lifecycle matches the app’s. - Less guidance from docs: Since the official docs don’t show this approach, new developers might need to spend a minute wrapping their heads around the setup.
Your instinct is spot-on: using a ViewController or standalone AuthManager as the delegate is the better choice for most apps. It leads to cleaner, more maintainable code and makes UI navigation trivial.
For a polished implementation, I recommend:
- Create a singleton
AuthManagerclass that implementsGIDSignInDelegateand handles all auth logic (login, logout, state checks). - Forward the URL redirect from
AppDelegatetoGIDSignIn. - Have your
LoginViewControllercommunicate withAuthManager(via closures, delegates, or notifications) to trigger logins and handle results, then update the UI accordingly.
Here’s a quick snippet of what that AuthManager might look like:
class AuthManager: NSObject, GIDSignInDelegate { static let shared = AuthManager() private override init() { super.init() GIDSignIn.sharedInstance().delegate = self // Firebase/Google Sign-In setup } func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) { if let error = error { NotificationCenter.default.post(name: .loginFailed, object: error) return } // Pass Google credentials to Firebase guard let auth = user.authentication else { return } let credential = GoogleAuthProvider.credential(withIDToken: auth.idToken, accessToken: auth.accessToken) Auth.auth().signIn(with: credential) { [weak self] result, error in if let error = error { NotificationCenter.default.post(name: .loginFailed, object: error) return } NotificationCenter.default.post(name: .loginSuccess, object: nil) } } func signOut() { GIDSignIn.sharedInstance().signOut() try? Auth.auth().signOut() } } // Extend Notification.Name for cleaner code extension Notification.Name { static let loginSuccess = Notification.Name("LoginSuccess") static let loginFailed = Notification.Name("LoginFailed") }
Then in your LoginViewController, listen for these notifications to handle UI updates:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(handleLoginSuccess), name: .loginSuccess, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleLoginFailed(_:)), name: .loginFailed, object: nil) } @objc func handleLoginSuccess() { performSegue(withIdentifier: "showMainScreen", sender: self) } @objc func handleLoginFailed(_ notification: Notification) { if let error = notification.object as? Error { let alert = UIAlertController(title: "Login Failed", message: error.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } }
This setup keeps your code decoupled, maintainable, and easy to extend as your app grows.
内容的提问来源于stack exchange,提问作者aspnet82

