Is there a way to specify that count should only publish on the main thread? I've seen docs that talk about setting up your Publisher using receive(on:)
, but in this case the @Publisher
wrapper hides that logic.
import SwiftUI
import Combine
class MyCounter: ObservableObject {
@Published var count = 0
public static let shared = MyCounter()
private init() { }
}
struct ContentView: View {
@ObservedObject var state = MyCounter.shared
var body: some View {
return VStack {
Text("Current count: \(state.count)")
Button(action: increment) {
HStack(alignment: .center) {
Text("Increment")
.foregroundColor(Color.white)
.bold()
}
}
}
}
private func increment() {
NetworkUtils.count()
}
}
public class NetworkUtils {
public static func count() {
guard let url = URL.parse("https://www.example.com/counter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let response = response as? HTTPURLResponse {
let statusCode = response.statusCode
if statusCode >= 200 && statusCode < 300 {
do {
guard let responseData = data else {
return
}
guard let json = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any] else {
return
}
if let newCount = json["new_count"] as? Int{
MyCounter.shared.count = newCount
}
} catch {
print("Caught error")
}
}
}
}
task.resume()
}
}
As you can see from my updated example, This is a simple SwiftUI view that has a button that when clicked makes a network call. The network call is asynchronous. When the network call returns, the ObservableObject MyCounter
is updated on a background thread. I would like to know if there is a way to make the ObservableObject
publish the change on the main thread. The only way I know to accomplish this now is to wrap the update logic in the network call closure like this:
DispatchQueue.main.async {
MyCounter.shared.count = newCount
}
Instead of using URLSession.shared.dataTask(with: request)
, you can use URLSession.shared.dataTaskPublisher(for: request)
(docs) which will allow you to create a Combine pipeline. Then you can chain the receive(on:)
operator as part of your pipeline.
URLSession.shared.dataTaskPublisher(for: request)
.map { response in ... }
...
.receive(on: RunLoop.main)
...
Also check out heckj's examples, I've found them to be very useful.
If you try to set value marked @Published
from a background thread you will see this error:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive
So you have to make sure anywhere you set the value that it this done on the main thread, the values will always be published on the main thread.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With