Swift:如何从闭包中取值以更新自定义DxUser对象?
Hey there, let's figure out why your DxUser is nil outside the Firestore closure—this is a super common gotcha with asynchronous operations, so you're not alone!
The Root Cause
Firestore's data fetch methods like getDocument() run asynchronously in the background. That means the code outside your closure executes before the Firestore request finishes and populates your DxUser. By the time you try to access the object outside the closure, the data hasn't arrived yet, hence the nil value.
Solutions to Fix This
Below are a few practical ways to handle this, depending on your iOS version and coding style:
1. Use a Completion Closure (Compatible with All iOS Versions)
Define a function that takes a completion callback to return the DxUser (or error) once the Firestore request completes. This ensures you only access the user data when it's ready.
// Define this in your view controller or a separate data manager func fetchCurrentUser(completion: @escaping (DxUser?, Error?) -> Void) { // First, make sure we have a logged-in user's ID guard let userId = Auth.auth().currentUser?.uid else { let authError = NSError(domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "No user is logged in"]) completion(nil, authError) return } // Fetch the user document from Firestore Firestore.firestore().collection("users").document(userId).getDocument { snapshot, error in if let error = error { completion(nil, error) return } // Parse the snapshot data into your DxUser object guard let userData = snapshot?.data(), let parsedUser = DxUser(dictionary: userData) else { let parseError = NSError(domain: "ParseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to parse user data"]) completion(nil, parseError) return } // Pass the ready-to-use DxUser to the completion completion(parsedUser, nil) } }
Then call this function in your view controller, and handle the user data inside the completion closure (or pass it to a dedicated UI update method):
override func viewDidLoad() { super.viewDidLoad() fetchCurrentUser { [weak self] user, error in guard let self = self else { return } // Handle errors first if let error = error { print("Oops, failed to fetch user: \(error.localizedDescription)") // Show an alert or update UI to notify the user return } // Now you have your non-nil DxUser! guard let currentUser = user else { print("No user data found in Firestore") return } // Assign it to your property and update the settings UI self.dxUser = currentUser self.updateSettingsPage(with: currentUser) } } // Helper method to update your settings UI elements private func updateSettingsPage(with user: DxUser) { // Make sure to update UI on the main thread (Firestore closures run in background) DispatchQueue.main.async { self.userNameTextField.text = user.name self.userEmailLabel.text = user.email // Add other UI updates here } }
2. Use Async/Await (iOS 15+, Swift 5.5+)
If you're targeting iOS 15 or later, async/await makes this code much cleaner and easier to read. No more nested closures!
// Async function to fetch the user func fetchCurrentUser() async throws -> DxUser { guard let userId = Auth.auth().currentUser?.uid else { throw NSError(domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "No user logged in"]) } // Await the Firestore document fetch let snapshot = try await Firestore.firestore().collection("users").document(userId).getDocument() guard let userData = snapshot.data(), let parsedUser = DxUser(dictionary: userData) else { throw NSError(domain: "ParseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to parse user data"]) } return parsedUser }
Call it using a Task in your view controller:
override func viewDidLoad() { super.viewDidLoad() Task { do { // Wait for the user to be fetched let currentUser = try await fetchCurrentUser() // Update UI on the main thread DispatchQueue.main.async { [weak self] in self?.dxUser = currentUser self?.updateSettingsPage(with: currentUser) } } catch { print("Error fetching user: \(error.localizedDescription)") // Handle error (e.g., show alert) } } }
Key Notes to Remember
- Always update UI on the main thread: Firestore's completion closures and async functions run in background threads, so wrap any UI updates in
DispatchQueue.main.async. - Don't forget error handling: Users might be logged out, the document might not exist, or there could be network issues—handle these cases gracefully.
- For real-time updates: If you want your settings page to automatically update when the user's Firestore document changes, use
addSnapshotListener()instead ofgetDocument(). This will trigger the closure every time the data is updated.
内容的提问来源于stack exchange,提问作者Seth Merritt

