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组件。
解决步骤
修正异步时序逻辑
移除按钮点击方法中对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) } // 其他代码... }确保主线程更新状态
在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) }可选优化:防止重复点击
可以在按钮点击时添加状态控制,避免用户重复触发请求:@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
相关产品推荐
相关产品推荐

