@Published properties and the main thread

I am working on a library, a Swift package. We have quite a few properties on various classes that can change and we think the @Published property wrapper is a good way to annotate these properties as it offers a built-in way to work with SwiftUI and also Combine.

Many of our properties can change on background threads and we've noticed that we get a purple runtime issue when setting the value from a background thread. This is a bit problematic for us because the state did change on a background thread and we need to update it at that time. If we dispatch it to the main queue and update it on the next iteration, then our property state doesn't match what the user expects. Say they "load" or "start" something asynchronously, and that finishes, the status should report "loaded" or "started", but that's not the case if we dispatch it to the main queue because that property doesn't update until the next iteration of the run loop.

There also isn't any information in the documentation for @Published that suggests that you must update it on the main thread. I understand why SwiftUI wants it on the main thread, but this property wrapper is in the Combine framework. Also it seems like SwiftUI internally could ask to receive the published updates on the main queue and @Published shouldn't enforce a specific thread.

One thing we are thinking about doing is writing our own property wrapper, but that doesn't seem to be ideal for SwiftUI integration and it's one more property wrapper that users of our package would need to be educated about.

Any thoughts on direction? Is there anyway to break @Published from the main thread?

Hi, you use the @Published wrapper for Observable Object and environment objects. They behave similarly to the @State wrapper, but can be used to get view updates from classes outside the current view, bind multiple views to one value and so on. So the only reason why you would want an @Published property is when you want to take advantage of the automatic view updates.

As you already mentioned, if any of your values change in the background you need to push these changes on the main thread. Because how should SwiftUI know that you want an update on your UI if the Object was modified by a background thread.

I don't white get why you would "confuse" the user by having the wrong status displayed. If you have a background task that needs to finish before changing the UI, you should use either a completion handler, or if your app targets iOS 15 you could use the async-await pattern...

We are using async. If I have an object that has a load() async method and a .loadStatus property (values might be something like not-loaded, loading, loaded), and the .load() can be called from and completed on an arbitrary thread (it might make a network request let's say). I like the pattern of our properties that can change being @Published as a built-in property wrapper for this situation. However, in our case where the method can complete on any given thread you end up with a bug like this:

foo.load()
XCTAssertEquals(foo.loadStatus, .loaded)

This fails, loadStatus is "loading" because it's Published and I have to DispatchQueue.main.async the load status update to avoid purple runtime errors.

it seems like SwiftUI internally could ask to receive the published updates on the main queue and @Published shouldn't enforce a specific thread.

I asked why publishing isn't done on the main thread automatically. The discussion is here for anyone interested.

@Published properties and the main thread
 
 
Q