Reputation: 2173
I would like to have some sort of global variable that is synchronized using @MainActor
.
Here's an example struct:
@MainActor
struct Foo {}
I'd like to have a global variable something like this:
let foo = Foo()
However, this does not compile and errors with Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
.
Fair enough. I've tried to construct it on the main thread like this:
let foo = DispatchQueue.main.sync {
Foo()
}
This compiles! However, it crashes with EXC_BAD_INSTRUCTION
, because DispatchQueue.main.sync
cannot be run on the main thread.
I also tried to create a wrapper function like:
func syncMain<T>(_ closure: () -> T) -> T {
if Thread.isMainThread {
return closure()
} else {
return DispatchQueue.main.sync(execute: closure)
}
}
and use
let foo = syncMain {
Foo()
}
But the compiler does not recognize if Thread.isMainThread
and throws the same error message again, Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
.
What's the right way to do this? I need some kind of global variable that I can initialize before my application boots.
Upvotes: 26
Views: 16405
Reputation: 7708
One way would be to store the variable within a container (like an enum
acting as an abstract namespace) and also isolating this to the main actor.
@MainActor
enum Globals {
static var foo = Foo()
}
An equally valid way would be to have a "singleton-like" static
property on the object itself, which serves the same purpose but without the additional object.
@MainActor
struct Foo {
static var shared = Foo()
}
You now access the global object via Foo.global
.
One thing to note is that this will now be lazily initialized (on the first invocation) rather than immediately initialized. You can however force an initialization early on by making any access to the object.
// somewhere early on
_ = Foo.shared
@MainActor
won't initialize static let
variables on the main thread. Use static var
instead.
@MainActor
struct Foo {
static let shared = Foo()
init() {
print("foo init is main", Thread.isMainThread)
}
func fooCall() {
print("foo call is main", Thread.isMainThread)
}
}
Task.detached {
await Foo.shared.fooCall()
}
// prints:
// foo init is main false
// foo call is main true
This is a bug (see issue 58270) that's been fixed in Swift 5.10.
Upvotes: 21