Accessing an actor's isolated state from within a SwiftUI view

I'm trying to understand a design pattern for accessing the isolated state held in an actor type from within a SwiftUI view.

Take this naive code:

actor Model: ObservableObject {
	@Published var num: Int = 0
	
	func updateNumber(_ newNum: Int) {
		self.num = newNum
	}
}

struct ContentView: View {
	@StateObject var model = Model()
	
    var body: some View {
		Text("\(model.num)") // <-- Compiler error: Actor-isolated property 'num' can not be referenced from the main actor
		Button("Update number") {
			Task.detached() {
				await model.updateNumber(1)
			}
		}
    }
}

Understandably I get the compiler error Actor-isolated property 'num' can not be referenced from the main actor when I try and access the isolated value. Yet I can't understand how to display this data in a view. I wonder if I need a ViewModel that observes the actor, and updates itself on the main thread, but get compile time error Actor-isolated property '$num' can not be referenced from a non-isolated context.

class ViewModel: ObservableObject {
	
	let model: Model
	@Published var num: Int
	let cancellable: AnyCancellable
	
	init() {
		let model = Model()
		self.model = model
		self.num = 0
		self.cancellable = model.$num // <-- compile time error `Actor-isolated property '$num' can not be referenced from a non-isolated context`
			.receive(on: DispatchQueue.main)
			.sink { self.num = $0 }
	}
	
}

Secondly, imagine if this code did compile, then I would get another error when clicking the button that the interface is not being updated on the main thread...again I'm not sure how to effect this from within the actor?

Answered by DTS Engineer in 683980022

Standard practice here is to tie your model actor to the main actor. I don’t have time to look up the details right now but I’m pretty sure that one of the following WWDC talks has all the details:

Share and Enjoy

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

Accepted Answer

Standard practice here is to tie your model actor to the main actor. I don’t have time to look up the details right now but I’m pretty sure that one of the following WWDC talks has all the details:

Share and Enjoy

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

One thing to note here - tying your whole mode to the main actor has consequences: for example not being able to conform it to Encodable for one.

A more nuanced approach is to create a global actor, and annotate only the properties that need to be isolated in the view model. I discuss this more here: SwiftUI macOS document app architecture in a concurrent world

Accessing an actor's isolated state from within a SwiftUI view
 
 
Q