Async/Await and updating state

When using conformance to ObservableObject and then doing async work in a Task, you will get a warning courtesy of Combine if you then update an @Published or @State var from anywhere but the main thread. However, if you are using @Observable there is no such warning. Also, Thread.current is unavailable in asynchronous contexts, so says the warning. And I have read that in a sense you simply aren't concerned with what thread an async task is on.

So for me, that begs a question. Is the lack of a warning, which when using Combine is rather important as ignoring it could lead to crashes, a pretty major bug that Apple seemingly should have addressed long ago? Or is it just not an issue to update state from another thread, because Xcode is doing that work for us behind the scenes too, just as it manages what thread the async task is running on when we don't specify?

I see a lot of posts about this from around the initial release of Async/Await talking about using await MainActor.run {} at the point the state variable is updated, usually also complaining about the lack of a warning. But ow years later there is still no warning and I have to wonder if this is actually a non issue. On some ways similar to the fact that many of the early posts I have seen related to @Observable have examples of an @Observable ViewModel instantiated in the view as an @State variable, but in fact this is not needed as that is addressed behind the scenes for all properties of an @Observable type.

At least, that is my understanding now, but I am learning Swift coming from a PowerShell background so I question my understanding a lot.

For what it's worth, I did find this thread, which offers a lot of insight and even seems to suggest a definitive answer as to a best practice, but again this is over 2 years ago. In that 25 months I would think Apple would fix the bug and we would be getting some sort of feedback when revising state from an async context, if indeed doing so was still an issue. That said, I think I am going to refactor a few things to use approach #3 from that thread, just to be on the safe side and develop good habits. But I still wonder if in fact this isn't necessary. And, if it IS necessary, and we no longer get any feedback from Xcode when we fail, I wonder what other issues I need to be aware of that Xcode isn't going to draw my attention to?

I have also seen it mentioned that @Observable and @MainActor can not be used together, so the old approach of just putting your whole ObservableObject ViewModel on the main actor no longer works. But I just verified that at least as of Xcode 16.1 I can use them both, and when I do my otherwise unmanaged State updates all occur on the main thread, while removing @MainActor on the VM causes all those state updates to occur on background threads. Again with no warning and no crashes. Yet. :)

So you are combining [hey hey!] a bunch of different things here, including:

  • Tasks and threads

  • Combine

  • Observation

  • SwiftUI

It’s hard to know which of these are critical to your goal and which are things you’ve bumped into while trying to find a solution. My general advice is:

  • Stick with Swift concurrency as much as possible.

  • If you’re working with Swift concurrency, don’t do anything with threads (or Dispatch queues for that matter). Combining the two is possible, but it’s easy to get it wrong [1].

  • Pick an observation mechanism, Combine or Observation, and try to stick with it as much as possible.

  • If you’re using Observation and SwiftUI, make your observable models @MainActor.

If you can post details about a specific problems you’re trying to solve, I’d be happy to offer more specific advice.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] This is an area that I’ve investigated in depth, so I’d be more than happy to answer questions about specific cases.

Async/Await and updating state
 
 
Q