Hi everyone! Could anyone please clarify how the concurrency checking works in my case? I have an actor that calls the foo method of a @MainActor class:
Now any types that conforms to MainActorProtocol is guarded by MainActor by default. Because that is the only way for default implementation to guarantee Sendable for MainActor isolated protocol.
@NeonTetra Thanks for the clarification! Also, I noticed that according to this Apple Documentation, if I have only async methods inside a Protocol I can mark @MainActor only MainActorClass and not MainActorProtocol.
Because async methods guarantee isolation by switching to the corresponding actor in the implementation.
But if I do that I am still getting the same error:
actor MyActor {
let property: MainActorProtocol
init(property: MainActorProtocol) {
self.property = property
}
func doSomething() async {
await property.foo() //Sending 'self.property' risks causing data races
}
}
protocol MainActorProtocol { // actor is not specified
func foo() async
}
@MainActor
class MainActorClass: MainActorProtocol {
func foo() async {
print("foo")
}
}
well you have to remember that only actor isolation is guarantee. But this also impiles MainActorProtocol is shared between different actor isolation. Which is in other word data race.
Also any async function that doesnt have isolation hint, Concurrency tries to dispatch that async method to the nonisolate global context.
So in swift 6 you have to guarantee one of 3 to be met.
Sendable : MainActorProtocol must be Sendable so that is safe to be shared across different actor isolation. ex) Locking, Global Actor, Actor etc...
disconnect the non sendable : detail is in here region-based-isolation, transferring. Which means you have send the whole instance to the different region and disconnect it. and take it back when it is done
do not cross the actor isolation
actor MyActor {
var property: MainActorProtocol
init(property: MainActorProtocol) {
self.property = property
}
func doSomething() async {
// property is in the same actor isolation
await property.foo(isolated: #isolation)
}
}
protocol MainActorProtocol { // actor is not specified
func foo(isolated actor: isolated (any Actor)?) async
}
@MainActor
class MainActorClass: MainActorProtocol {
func foo(isolated actor: isolated (any Actor)? = #isolation) async {
print("foo")
}
}
But what is an example of using a protocol without crossing the actor isolation?
Since we usually pass protocols values from outside, should we always use Sendable for protocols?
It looks like in Swift 6 using protocols is much more complicated. Especially if you have a big dependency hierarchy. You either need to pass an isolated actor through the whole hierarchy or mark every dependency in the hierarchy as Sendable. Both options are not very handy. Much easier just to remove all protocols and use concrete types everywhere :)