I practice in iOS16, but this form of coding is not confirmed to be used in production. There are also some confusing.
struct TestView: View {
@StateObject private var viewModel = TestViewModel()
var body: some View {
VStack {
Button("mutatingFight") {
Task {
// calling a SideActor method on MainActor context will be await even without explicitly async mark
await viewModel.mutatingFight()
}
}
Button("mutatingSpell") {
Task {
// Innerly, asking for @MainActor on SideActor method will be async method
await viewModel.mutatingSpell()
}
}
showMainActorProperty(viewModel.counter)
}
.frame(width: 300, height: 400)
}
func showMainActorProperty(_ property: Int) -> some View {
Text("\(property)")
}
}
// final class? Sendable?
actor TestViewModel: ObservableObject {
@MainActor @Published
var counter: Int = 0
@SideActor
private var spell: Int = 0 {
didSet {
pushToMain()
}
}
@SideActor
private var fight: Int = 0 {
didSet {
pushToMain()
}
}
@SideActor
func mutatingSpell() async {
if await counter >= 2 {
spell -= 2
}
}
@SideActor
func mutatingFight() {
fight += 1
}
@SideActor
func pushToMain() {
let value = spell + fight
Task { @SideActor in
await MainActor.run {
counter = value
}
}
}
}
/// lower level above UI's
@globalActor
public actor SideActor: GlobalActor {
public typealias ActorType = SideActor
static public let shared = SideActor()
static private let _sharedExecutor = SideExecutor()
static public let sharedUnownedExecutor: UnownedSerialExecutor = _sharedExecutor.asUnownedSerialExecutor()
public let unownedExecutor: UnownedSerialExecutor = sharedUnownedExecutor
final private class SideExecutor: SerialExecutor {
private static let dispatcher = DispatchQueue(label: "MySideExecutor", qos: .default)
internal func enqueue(_ job: UnownedJob) {
print(Self.self, #function)
Self.dispatcher.async {
job.runSynchronously(on: sharedUnownedExecutor)
}
}
internal func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
}
}