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

Swift开发中Firebase注册时saveDataToFirestore因handler重复调用如何修复

问题根源

  • 异步逻辑时序错误:Auth.auth().createUser 是异步执行的方法,你在调用后直接同步执行 saveDataToFirestore,此时注册请求尚未返回,用户认证状态还未完成更新。
  • 状态监听器使用不当:addStateDidChangeListener 是持久化的全局监听器,添加后只要不手动移除,任何Auth状态变化(包括注册流程中的内部状态切换、临时用户到正式用户的变更)都会触发回调,这就是日志中entry 2打印两次的原因。两次返回的用户ID不一致,是注册流程中Auth内部先生成临时用户、再替换为正式注册用户导致的。
  • 监听器未释放:你存储了监听器的handle但从未调用removeStateDidChangeListener移除,不仅会导致重复回调,还会引发内存泄漏。

修复方案

核心逻辑是将用户数据写入操作和注册结果绑定,不要用全局状态监听器处理单次注册后的写入需求,修改后的代码如下:

class UserViewModelTEMP : ObservableObject {
    @Published var user = [UserTEMP]()
    var handle: AuthStateDidChangeListenerHandle?
    private let db = Firestore.firestore()
    
    // 若需要全局监听用户登录状态,统一在init中添加
    init() {
        handle = Auth.auth().addStateDidChangeListener { [weak self] (auth, user) in
            guard let self = self else { return }
            if let user = user {
                // 此处仅做全局状态更新,不执行业务写入操作
                print("当前登录用户ID:\(user.uid)")
            } else {
                self.user = []
            }
        }
    }
    
    // 实例销毁时移除监听器,避免内存泄漏和无效回调
    deinit {
        if let handle = handle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }
    
    func signUp (firstName: String, lastName: String, email: String, password: String, reenterPassword: String, handler: @escaping AuthDataResultCallback) {
        // 密码校验不通过时主动回调错误
        if reenterPassword != password {
            let error = NSError(domain: "SignUpDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "两次输入密码不一致"])
            handler(nil, error)
            return
        }
        // 发起注册请求
        Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
            guard let self = self else { return }
            // 先将注册结果回调给View层处理UI状态
            handler(result, error)
            
            // 注册成功后再执行写入Firestore逻辑
            if let error = error {
                print("注册失败:\(error.localizedDescription)")
                return
            }
            guard let registeredUser = result?.user else {
                print("未获取到注册成功的用户信息")
                return
            }
            // 直接传入注册接口返回的确定用户ID,不需要再监听状态
            self.saveDataToFirestore(firstName: firstName, lastName: lastName, email: email, uid: registeredUser.uid)
        }
    }
    
    func saveDataToFirestore(firstName: String, lastName: String, email: String, uid: String) {
        print("开始写入用户数据到Firestore")
        let ref = db.collection("users").document(uid)
        ref.setData([
            "first_name" : firstName,
            "last_name"  : lastName,
            "email"      : email
        ]) { err in
            if let err = err {
                print("写入失败:\(err.localizedDescription)")
            } else {
                print("写入成功,文档ID:\(ref.documentID)")
            }
        }
    }
}

修复说明

  • 写入逻辑移到注册成功回调内部,确保只有注册完成拿到确定的用户ID后才执行写入,不会出现ID不一致问题
  • 移除了写入方法内的状态监听器,从根源避免重复触发写入
  • 补充了密码不匹配场景的错误回调,避免View层无响应
  • 新增监听器的移除逻辑,解决内存泄漏问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.20 01:15:03