I'm working on a Swift app's login process and I'd like to know if I should include both success and failure handlers to the function, which checks if the user has successfully logged in or not.
What I want to do here is trigger the successCompletion() block only when the user successfully logged in and otherwise display an Alert with a message. I feel the login function takes multiple arguments, and I think it is kind of hard to figure out it handles two completion handlers from LoginViewController. So, should I include both error and success handlers in the function or better to separate them somehow?
In my LoginViewController,
class LoginViewController: UIViewController {
private let loginView = LoginView()
@objc func loginButtonAction() {
let idInputValue = loginView.loginIdTextField.text!
let passInputValue = loginView.passwordTextField.text!
LoginAPI().login(with: idInputValue, and: passInputValue, errorCompletion: show(message:)) { _ in
let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as! SceneDelegate
sceneDelegate.setRootVC(to: HomeViewController())
}
func showAlert(message: String) {
let okAction = UIAlertAction(title: Alert.ButtonTitle.ok, style: .default, handler: nil)
showAlert(title: Alert.Title.loginError, message: message, actions: [okAction], style: .alert)
}
}
and my LoginAPI class,
import Alamofire
class LoginAPI {
static var accountInfo: AccountInfo?
private let loginEndpoint = "https://example.com"
func login(with id: String, and password: String, errorCompletion: @escaping (_ m: String) -> Void?, successCompletion: @escaping () -> Void) {
let parameter: [String: Any] = ["id": id, "password": password]
AF.request(loginEndpoint, method: .post, parameters: parameter)
.validate()
.responseJSON { response in
guard let data = response.data else {
errorCompletion(response.error)
return
}
let decoder: JSONDecoder = JSONDecoder()
var accountInfo: AccountInfo?
do {
accountInfo = try decoder.decode(AccountInfo.self, from: data)
} catch {
errorCompletion(error)
}
LoginAPI.accountInfo = accountInfo
DispatchQueue.main.async {
successCompletion()
}
}
}
}
updated the function
func login(with id: String, and password: String, completed: @escaping (Result<AccountInfo, LoginError>) -> Void) {
let parameter: [String: Any] = ["id": id, "password": password]
AF.request(loginEndpoint, method: .post, parameters: parameter)
.responseDecodable(of: AccountInfo.self) { response in
switch(response.result) {
case .success(let data):
completed(.success(data))
case .failure(let error):
completed(.failure(.test))
}
}
}
It's common to use Result
. By returning the result you keep the responsibility of login()
tight and handle the result somewhere else. And it indeed also avoids the unclarity of two completion blocks.
Other remarks: You don't need DispatchQueue.main.async
because AF.request
completion block is already on the main thread.