As the code is written above, the problem would be that this…
DispatchQueue.main.async {
// ...
self.status = "X"
sleep(3)
self.status = "bar"
}
is blocking the main thread. The @Published var may very be sending its updates, but because the main thread is blocked, you can't actually see that update in the UI. When you push a change to a @Published var, it sends a willSet event that lets the UI record the previous value. It then schedules an update on the next pass of the run loop to compare the previous value to the current value. That lets it schedule an animation from the previous state to the current state, for example.
But as you've got the code written there, the main thread is blocked until the entire operation is done. That means that UI will never update to see "X" in the status field; by the time that next run loop pass is actually able to run, we're already to "bar".
I would look at whether it's possible to move the long-running blocking operation to a background thread. You could then either use a completion handler to finish updating the UI, or wrap it in an async function. That is, I'm suggesting you use one of the two following patterns:
// Option A (Modern Concurrency)
Task { @MainActor in
self.status = "X"
await runLongOperation(input: "some input here")
self.status = "bar"
}
// Since this is an async function, it will never run
// on the main thread unless specifically annotated
// with @MainActor. We haven't done that, so this
// automatically runs in the background.
func runLongOperation(input: String) async {
sleep(3)
}
or...
// Option B (Legacy Concurrency)
DispatchQueue.main.async {
self.status = "X"
runLongOperation(input: "some input", completion: {
DispatchQueue.main.async {
self.status = "bar"
}
}
}
func runLongOperation(input: String, completion: @escaping ()->Void) {
DispatchQueue.global().async {
sleep(3)
completion()
}
}