You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift:如何从闭包中取值以更新自定义DxUser对象?

Fixing DxUser Nil Issue Outside Firestore Closure

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 of getDocument(). This will trigger the closure every time the data is updated.

内容的提问来源于stack exchange,提问作者Seth Merritt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:16:52