@StateObject Publishing changes from background thread

Hi. I have a button that triggers a request to a webservice:

struct ContentView: View {
    @StateObject private var state: State = State()

    var body: some View {
        VStack {
            Button("Request", action: { makeRequest() })
            Text(state.response)
        }
    }

    private func makeRequest() {
        URLSession.shared.dataTask(with: URL(string: "https://my-json-server.typicode.com/typicode/demo/posts/1")!, completionHandler: { data, _, _ in
            state.response = String(bytes: data!, encoding: .utf8)!
        })
        .resume()
    }
}

and an ObservableObject to store the response in:

class State: ObservableObject {
    @Published var response: String = ""
}

.

It seems to workd, but, I get the error message

[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.

So I was wondering if this is the correct way to store the responses of HTTP requests in a StateObject. I also couldn't find anything about receive(on:).

Answered by Thisisbohau in 710019022

Hi, based on the code you shared I would change two things. If the class contains nothing but a single @Published variable I would just declare a single variable in your ContentView:

struct ContentView: View {
    @State private var response: String = ""
...}

And to get rid of the error you have to publish the changes on the main thread(the error warns you because a data task is running on a background thread and also completes on one and therefore any changes you want to display need to be on the main thread).

private func makeRequest() {
        URLSession.shared.dataTask(with: URL(string: "https://my-json-server.typicode.com/typicode/demo/posts/1")!, completionHandler: { data, _, _ in

       DispatchQueue.main.async{
             self.response = String(bytes: data!, encoding: .utf8)!
        }
   
        })
        .resume()
    }

The first part with the class is optional but there is no need for a separate class with a single value :)

Take care David

Accepted Answer

Hi, based on the code you shared I would change two things. If the class contains nothing but a single @Published variable I would just declare a single variable in your ContentView:

struct ContentView: View {
    @State private var response: String = ""
...}

And to get rid of the error you have to publish the changes on the main thread(the error warns you because a data task is running on a background thread and also completes on one and therefore any changes you want to display need to be on the main thread).

private func makeRequest() {
        URLSession.shared.dataTask(with: URL(string: "https://my-json-server.typicode.com/typicode/demo/posts/1")!, completionHandler: { data, _, _ in

       DispatchQueue.main.async{
             self.response = String(bytes: data!, encoding: .utf8)!
        }
   
        })
        .resume()
    }

The first part with the class is optional but there is no need for a separate class with a single value :)

Take care David

@StateObject Publishing changes from background thread
 
 
Q