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

Swift单例AuthManager需两次执行函数才变更状态问题排查

}

enum AuthState {
case login
case signUp
case error(err: ErrorDescription?)
}

class AuthManager: ObservableObject {

static let shared = AuthManager()
@Published var authState: AuthState = .login
var delegate: AuthManagerDelegate?

private init() {}

func verifEmail(password: String, email: String) {
    let attributes = [
        AuthUserAttribute(.email, value: email)
    ]
    let options = AuthSignUpRequest.Options(userAttributes: attributes)
    
    _ = Amplify.Auth.signUp(
        username: email,
        password: password,
        options: options
    ) { [unowned self] result in
        
        switch result {
            
        case .success(let signUpResult):
            print("Sign up result:", signUpResult)
            
            switch signUpResult.nextStep {
            case .done:
                print("Finished sign up")
                
            case .confirmUser(let details, _):
                print(details ?? "no details")
            }
            
        case .failure(let error):
            self.authState = .error(err: error.errorDescription)
            self.delegate?.errDuringAuth(errTitle: "Sign Up Error", errMsg: error.errorDescription)
        }
    }
}

}

### CreateAccountViewController 代码
```swift
class CreateAccountViewController: UIViewController {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var confirmPwdTextField: UITextField!
    
    override func viewDidLoad() {
        ...
        AuthManager.shared.delegate = self
    }
    
    @IBAction func continueBtnPushed(_ sender: UIButton) {
        if let email = emailTextField.text, let pwd = passwordTextField.text, let confPwd = confirmPwdTextField.text {
            if (pwd.count >= 8) && (pwd == confPwd) {
                
                AuthManager.shared.verifEmail(password: pwd, email: email)
                
                switch AuthManager.shared.authState {
                case .login:
                    print("login state")
                case .error(_):
                    print("error state")
                default:
                    print("other state")
                }
                
            }
        } else {
            print("error fetching textfield text")
        }
    }
}

// MARK: - AuthManagerDelegate
extension CreateAccountViewController: AuthManagerDelegate {
    
    func errDuringAuth(errTitle: String?, errMsg: ErrorDescription?) {
        DispatchQueue.main.async {
            let errAlert = UIAlertController(title: errTitle ?? "Error", message: errMsg ?? "Unknown error", preferredStyle: .alert)
            let dismissAction = UIAlertAction(title: "OK", style: .default, handler: nil)
            
            errAlert.addAction(dismissAction)
            self.present(errAlert, animated: true, completion: nil)
        }
    }
}

分析与解决

问题根源

这不是线程问题,而是异步操作的时序问题:

  • Amplify.Auth.signUp是异步方法,调用后会立刻返回,不会等待网络请求完成。
  • 你在调用verifEmail后立刻同步检查authState,此时异步请求还未完成,authState保持初始的.login状态。
  • 第一次点击时,异步请求完成后会将authState改为.error,但此时按钮点击的代码已经执行完毕,所以看不到对应效果;第二次点击时,authState已经是.error,因此能立刻触发逻辑。

另外存在一个潜在问题:修改@Published属性authState时未确保在主线程执行——Amplify的回调可能运行在后台线程,而@Published属性的更新必须在主线程才能正确通知UI组件。

解决步骤

  1. 修正异步时序逻辑
    移除按钮点击方法中对authState的同步检查,改为通过订阅@Published属性的变化来处理状态更新:

    import Combine
    
    class CreateAccountViewController: UIViewController {
        private var cancellables = Set<AnyCancellable>()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            AuthManager.shared.delegate = self
            
            // 订阅authState的变化,确保在主线程处理
            AuthManager.shared.$authState
                .receive(on: DispatchQueue.main)
                .sink { [weak self] state in
                    switch state {
                    case .error(_):
                        print("error state")
                    case .login:
                        print("login state")
                    default:
                        print("other state")
                    }
                }
                .store(in: &cancellables)
        }
        
        // 其他代码...
    }
    
  2. 确保主线程更新状态
    在AuthManager的错误回调中,将修改authState和调用delegate的代码放到主线程:

    case .failure(let error):
        DispatchQueue.main.async {
            self.authState = .error(err: error.errorDescription)
            self.delegate?.errDuringAuth(errTitle: "Sign Up Error", errMsg: error.errorDescription)
        }
    
  3. 可选优化:防止重复点击
    可以在按钮点击时添加状态控制,避免用户重复触发请求:

    @IBAction func continueBtnPushed(_ sender: UIButton) {
        sender.isEnabled = false
        
        guard let email = emailTextField.text, 
              let pwd = passwordTextField.text, 
              let confPwd = confirmPwdTextField.text,
              pwd.count >= 8, pwd == confPwd else {
            sender.isEnabled = true
            print("error fetching textfield text")
            return
        }
        
        AuthManager.shared.verifEmail(password: pwd, email: email)
        
        // 可以在AuthManager的回调中恢复按钮状态,比如通过delegate新增方法
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 15:40:21