I'm making an app, with UIKit-Storyboard, that simulates a login screen: The moment the user enters the login data (username and password) and then clicks the login button, a class responsible for encapsulating requests with Alamofire invokes a method whose return is the result of the POST request (I use in this project the Clean Swift Architecture, so in this case the Interactor delegates this function to a Worker and it returns an object).
My problem is that I need to use the return of this method (the result of the POST request) to display some data on the screen. However, since the requests with Alamofire are asynchronous, the function returns nil before the request fills the object.
My classes looks like this:
Class LoginInteractor (where I expect the result of the request but always returns nil):
class LoginInteractor: LoginBusinessLogic {
var httpRequestWorker: LoginHTTPRequestWorker!
func applyBusinessLogic(request: Login.Login.Request) {
let user = User(username: request.fields.username,
password: request.fields.password)
// (1)
if let userData = self.doLoginInEndpoint(with: user) {
// NEVER FALLS HERE BECAUSE userData IS ALWAYS NIL
var userAccount: UserAccount?
var errorMessage: ErrorMessage?
if let _ = userData.userAccount.userId {
userAccount = UserAccount(extractedFrom: userData)
}
if let _ = userData.error.code {
errorMessage = ErrorMessage(extractedFrom: userData)
}
// Do some work with userData and delegates to presenter
....
}
}
// (2)
private func doLoginInEndpoint(with user: User) -> UserData? {
httpRequestWorker = LoginHTTPRequestWorker()
return httpRequestWorker.doLogin(with: user)
}
}
Class LoginHTTPRequestWorker:
class LoginHTTPRequestWorker {
private let group = DispatchGroup()
// (3)
func doLogin(with user: User) -> UserData? {
var userData: UserData?
makeRequest(with: user) { data in
userData = data
print(userData)
self.group.leave()
}
return userData
}
// (4)
private func makeRequest(with user: User, completion: @escaping (UserData) -> ()) {
let endpoint = // "a string representing an endpoint"
let headers: HTTPHeaders = [.contentType("application/json; charset=utf-8")]
let request = AF.request(endpoint,
method: .post,
parameters: UserParameters(user: user.username,
password: user.password),
encoder: JSONParameterEncoder.default,
headers: headers)
group.enter()
request.responseDecodable(of: UserData.self) { response in
self.group.notify(queue: DispatchQueue.main) {
completion(response.value!)
}
}
}
}
Struct UserParameters (represents the parameters of the body POST request):
struct UserParameters: Encodable {
let user: String
let password: String
}
Struct UserData (represents the data from JSON response):
struct UserData: Decodable {
// some var declarations such as id, name etc
}
Enum Login (represents the objects passed between ViewController, Interactor and Presenter):
enum Login {
struct LoginFields {
var username: String
var password: String
}
enum Login {
struct Request {
var fields: LoginFields
}
struct Response {
var user: UserAccount?
var error: ErrorMessage?
}
struct ViewModel {
var user: UserAccount?
var error: ErrorMessage?
}
}
}
Is there a way to return an object after the POST request is over? Or is there a better way to do this task? I searched around here, and found some solutions like: using DispatchGroup.wait(), DispatchGroup.notify(queue: DispatchMain), and using closures, but these solutions didn't work for me.
I apologize in advance as I am new to Swift and Stack Overflow but I would be very happy if someone could at least point me in a direction. Thanks!