I am trying to build a Singleton ApiManager class that can build and send all of my API requests for me and send the response data to awaiting request object. In SwiftUI when I see, say a user successfully login, I would like to switch the views to maybe a dashboard. The problem is that I am using an ObservableObject to publish the "isLoggedIn" Bool and I make all of my backend requests on a background thread. I am getting this error
[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
Here is how I perform API requests
func performRequest(with request: some AbstractRequest) {
var link = host request.endpoint
appendParameters(to: &link, with: request.params)
guard let url = URL(string: link) else {
print("Failed to generate url from \(link)")
return
}
let task = URLSession.shared.dataTask(with: url){ data, response, error in
guard let data = data, error == nil else{
print("\(error?.localizedDescription ?? "")")
return
}
request.completion(data: data)
}
task.resume()
}
Here is an example completion handler (Bonus point if you can tell me why I get this warning
Conditional cast from '[LoginResponse]' to '[LoginResponse]' always succeeds
on the cast line but if I remove the cast it complains.)
func completion(data: Data)
{
do{
guard let response = try JSONDecoder().decode([LoginResponse].self, from: data) as? [LoginResponse] else { return }
delegate.responseReceived(response: response)
}catch let error{
print("\(error.localizedDescription)")
}
}
Here is what my LoginManager looks like
class LoginManager: ObservableObject, ApiRequestDelegate{
static var shared = LoginManager()
var token = ""
@Published var isLoggedIn: Bool = false
func responseReceived(response: Any) {
guard let response = response as? LoginResponse, response.token != -1 else { return }
token = String(response.token)
isLoggedIn = true
}
}
And finally my ContentView
struct ContentView: View {
@ObservedObject var loginManager = LoginManager.shared
var body: some View {
if !loginManager.isLoggedIn {
LoginView()
}else {
OpenLinkButton().ignoresSafeArea()
}
}
}
CodePudding user response:
Your completion handler inside URLSession.shared.dataTask
is called on a background thread. So everything that follows is on a background thread.
The most simple solution would be to dispatch UI changes to the main Thread. e.g.:
DispatchQueue.main.async{
isLoggedIn = true
}
another solution would be to decorate the function interacting with the UI with @MainActor
atribute. e.g.:
@MainActor
func responseReceived(response: Any) {