I found that this code compiles, which I didn't expect:
@MainActor
@propertyWrapper
struct Wrapped<T> {
var wrappedValue: T
}
class MyClass {
@Wrapped var string: String
init(string: String) {
self.string = string
}
func doSomething() {
print(string)
}
}
How can this be OK? The initializer and doSomething must both need to be on the main actor to interact with the property wrapper, but nothing in MyClass
is marked @MainActor
?
Well, it turns out when you try to use it, that actually, both the initializer and the method have been implicitly marked @MainActor
:
await Task.detached {
MyClass(string: "hi").doSomething()
// ^ error: expression is 'async' but is not marked with 'await'
// note: calls to instance method 'doSomething()' from outside of its actor context are implicitly asynchronous
// note: calls to initializer 'init(string:)' from outside of its actor context are implicitly asynchronous
}.value
Adding the await
fixes things and all the code compiles:
await Task.detached {
await MyClass(string: "hi").doSomething()
}.value
My question is, is this intentional? That spooky action-at-a-distance in my codebase can spread @MainActor
like wildfire? And if it is intentional, is it desirable? Wouldn't it be better to ask me to mark these methods @MainActor
myself, so I understand the implications of what I've done?