Skip to content

Instantly share code, notes, and snippets.

@inamiy
Last active May 20, 2023 02:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inamiy/18585ead2df825e2c00c5abb6967fca0 to your computer and use it in GitHub Desktop.
Save inamiy/18585ead2df825e2c00c5abb6967fca0 to your computer and use it in GitHub Desktop.
`@propertyWrapper` + `@MainActor var wrappedValue` which propagates `@MainActor` context to the encapsulating type. https://twitter.com/inamiy/status/1659530309501853696
@MainActor
class ViewModel: ObservableObject {}
@propertyWrapper
struct Wrapper<T> {
var wrappedValue: T
}
@propertyWrapper
struct MainWrapper<T> {
@MainActor var wrappedValue: T
}
//----------------------------------------
struct Foo1 {
var viewModel: ViewModel = ViewModel() // ERROR: Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
}
struct Foo2 {
@StateObject
var viewModel: ViewModel = ViewModel() // Compile OK
}
struct Foo3 {
@Wrapper
var viewModel: ViewModel = ViewModel() // ERROR: Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
}
struct Foo4 {
@MainWrapper
var viewModel: ViewModel = ViewModel() // Compile OK
}
struct Foo5 {
@MainWrapper
var viewModel: ViewModel = ViewModel() // Compile OK
// Compile OK, even though it looks same as Foo1,
// because `Foo5.init` will automatically gain `@MainActor` by `@MainWrapper`,
// and `MainWrapper.init` will automatically gain `@MainActor` by `@MainActor var wrappedValue`.
var viewModel2: ViewModel = ViewModel()
}
// NOTE: Requires `@MainActor`, although there is no `@MainActor Foo5` or `@MainActor init`.
func testFoo5() {
_ = Foo5() // ERROR: Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
}
@inamiy
Copy link
Author

inamiy commented May 19, 2023

With above given, we can explain why the following code causes errors where error messages are different:

@MainActor
class ViewModel: ObservableObject {
    var value: Int = 0
}

actor MyActor {
    var value: Int = 0
}

struct MyView {
    @StateObject
    var viewModel: ViewModel = ViewModel()

    func foo() -> some View {
        Text("\(viewModel.value)") // Compile OK
    }
}

struct MyView2 {
    @StateObject
    var viewModel: ViewModel = ViewModel()

    let myActor = MyActor()

    func foo() -> some View {
        Text("\(myActor.value)") // ERROR: Actor-isolated property 'value' can not be referenced from the main actor
    }
}

struct MyView3 {
    let myActor = MyActor()

    func foo() -> some View {
        Text("\(myActor.value)") // ERROR: Actor-isolated property 'value' can not be referenced from a non-isolated context
    }
}

@inamiy
Copy link
Author

inamiy commented May 20, 2023

swift-evolution/0316-global-actors.md at main · apple/swift-evolution · GitHub

A struct or class containing a wrapped instance property with a global actor-qualified wrappedValue infers actor isolation from that property wrapper:

@propertyWrapper
struct UIUpdating<Wrapped> {
  @MainActor var wrappedValue: Wrapped
}

struct CounterView { // infers @MainActor from use of @UIUpdating
  @UIUpdating var intValue: Int = 0
}

@inamiy
Copy link
Author

inamiy commented May 20, 2023

Another interesting fact when having multiple @globalActor-ed @propertyWrappers: https://twitter.com/kntkymt/status/1659595057929031680

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment