I'm learning about the new concurrency model that Swift offers with its actor
mechanism and I'm having a hard time figuring out the concrete use of a globalActor
(except from @MainActor
).
Given this example:
@globalActor
actor TestGlobalActor {
static var shared = TestGlobalActor()
var data = [1, 2, 3]
}
@TestGlobalActor
class TestClass {
func handleData() {
print(TestGlobalActor.shared.data) // 🛑 Actor-isolated property 'data' can not be referenced from global actor 'TestGlobalActor'
}
}
why cannot I access the data
property of my global actor given the fact that the method handleData
is supposed to perform on TestGlobalActor
Is a global actor supposed to be used in this way, by adding methods and variables in it? or is it just a standalone piece of boilerplate code that is not meant to be extended?
I was able to get the result I expected the above code to produce, but it requiered me to add another inbetween singleton class:
@globalActor
actor TestGlobalActor {
static var shared = TestGlobalActor()
}
@TestGlobalActor
class Singleton {
static var shared = Singleton()
private init() {}
var data = [1, 2, 3]
}
@TestGlobalActor
class TestClass {
func handleDate() {
print(Singleton.shared.data) // ✅
}
}
By annotating something with a global actor, that thing is isolated to the shared
instance of the global actor. But there could be other instances of the actor!
So just by declaring shared
as a var
, the code is not really safe. shared
could easily be set to some other instance later down the line, and I don't know what will happen after that. Accessing the var shared
will give you a warning if you use the -warn-concurrency
option.
Let's make that a let
:
@globalActor
actor TestGlobalActor {
static let shared = TestGlobalActor()
var data = [1, 2, 3]
}
But it is still not safe accessing data
from handleData
. Swift isn't smart enough to see that this access of data
accesses the same instance of TestGlobalActor
that is used when you use it as a global actor.
In other words, as far as actor isolation is concerned, TestGlobalActor.shared.data
"looks the same" as TestGlobalActor().data
. In either case, Swift thinks that the instance on which data
is accessed is not the same instance as the global actor.
What you can do is mark data
as @TestGlobalActor
too:
@globalActor
actor TestGlobalActor {
static let shared = TestGlobalActor()
@TestGlobalActor
var data = [1, 2, 3]
}
This makes your code compile, but it also means that the data
of other TestGlobalActor
instances are also isolated to the global actor, and not the individual actor instances, which is quite weird.
Of course, if you intend on only having one instance of TestGlobalActor
, then there is no problem. Write a private init() {}
in there while you're at it.
If you do intend on having multiple instances of TestGlobalActor
, then using a global actor probably isn't suitable. Move data
somewhere else, or move whatever uses data
into TestGlobalActor
.