Home > other >  Swift - Function needs to be executed twice before singleton property changes
Swift - Function needs to be executed twice before singleton property changes

Time:10-08

I have a Singleton class AuthManager that is used to manage AWS Amplify authentication in an app I'm working on. The singleton class has a property that stores the state of authentication and a delegate property to alert any view controller that uses the AuthManager. Here is the Authentication manager code:

protocol AuthManagerDelegate {
    // to initiate alert display to user in case of error
    func errDuringAuth(errTitle: String?, errMsg: ErrorDescription?)
    ...
}

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)
            }
        }
    }
}

Now over in the view controller, after filling out the registration form and clicking the Confirm button in the UI I expect the alert to show up on the first click because the email already exists in my user pool (I'm testing error messages).

After clicking the button the alert does not show and AuthManager.shared.authState remains unchanged (.login), when it should be changed to .error

It's only when I dismiss the alert and click the Continue button again that the state changes to error

Here is the the ViewController code for more context:

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)
        }
    }
}

So after calling AuthManager.shared.verifEmail() in continueBtnPushed I expect the state to be changed from .login which is what it is initially to .error, but that only happens the second time I push the button. Is it a threading issue? I'm very confused...

CodePudding user response:

The state you check after calling verifyEmail immediately will not change, because you reassign the state in the block after a network request. HTTP request needs some time to execute, and this operation is asynchronized. That means, after the verifyEmail() method is called, it will be returned immediately, and the following switch code will execute. After a while - not fixed, the request block will be called, and the state value will be changed.

  • Related