如何使用@MainActor 初始化全局变量?
How do I initialize a global variable with @MainActor?
我想要某种使用 @MainActor
同步的全局变量。
这是一个示例结构:
@MainActor
struct Foo {}
我想要一个像这样的全局变量:
let foo = Foo()
但是,这不会编译并且 Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
出错。
很公平。我试过像这样在主线程上构建它:
let foo = DispatchQueue.main.sync {
Foo()
}
编译成功!但是,它因 EXC_BAD_INSTRUCTION
而崩溃,因为 DispatchQueue.main.sync
不能在主线程上 运行。
我还尝试创建一个包装函数,例如:
func syncMain<T>(_ closure: () -> T) -> T {
if Thread.isMainThread {
return closure()
} else {
return DispatchQueue.main.sync(execute: closure)
}
}
并使用
let foo = syncMain {
Foo()
}
但编译器无法识别 if Thread.isMainThread
并再次抛出相同的错误消息,Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
。
正确的做法是什么?我需要某种可以在应用程序启动前初始化的全局变量。
一种方法是将变量存储在容器中(如 enum
作为抽象命名空间)并将其与主要参与者隔离。
@MainActor
enum Globals {
static let foo = Foo()
}
一个同样有效的方法是在对象本身上有一个“类单例”static
属性,它具有相同的目的,但没有额外的对象。
@MainActor
struct Foo {
static let global = Foo()
}
您现在可以通过 Foo.global
访问全局对象。
需要注意的一件事是现在将延迟初始化(在第一次调用时)而不是立即初始化。
但是,您可以通过对该对象进行任何访问来提前强制初始化。
// somewhere early on
_ = Foo.global
Swift 5.5 和 Swift 5.6 中的错误
TL;DR:@MainActor 有时不会在主线程上调用 static let
变量。请改用 static var
。
虽然这可以编译并工作,但似乎这可能会在主线程之外调用初始化程序,然后在初始化程序内部进行任何调用。
@MainActor
struct Bar {
init() {
print("bar init is main", Thread.isMainThread)
}
func barCall() {
print("bar call is main", Thread.isMainThread)
}
}
@MainActor
struct Foo {
@MainActor
static let global = Foo()
init() {
print("foo init is main", Thread.isMainThread)
let b = Bar()
b.barCall()
}
func fooCall() {
print("foo call is main", Thread.isMainThread)
}
}
Task.detached {
await Foo.global.fooCall()
}
// prints:
// foo init is main false
// bar init is main false
// bar call is main false
// foo call is main true
这是一个错误(参见 SR-16009)。
一种解决方法是始终确保初始化发生在 @MainActor
上下文中,我们可以通过在我们第一次调用的地方注释闭包来做到这一点。
Task.detached { @MainActor in
await Foo.global.fooCall()
}
或者,您可以使用 static var
而不是 static let
,因为在这种情况下会强制执行正确的隔离行为。
我想要某种使用 @MainActor
同步的全局变量。
这是一个示例结构:
@MainActor
struct Foo {}
我想要一个像这样的全局变量:
let foo = Foo()
但是,这不会编译并且 Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
出错。
很公平。我试过像这样在主线程上构建它:
let foo = DispatchQueue.main.sync {
Foo()
}
编译成功!但是,它因 EXC_BAD_INSTRUCTION
而崩溃,因为 DispatchQueue.main.sync
不能在主线程上 运行。
我还尝试创建一个包装函数,例如:
func syncMain<T>(_ closure: () -> T) -> T {
if Thread.isMainThread {
return closure()
} else {
return DispatchQueue.main.sync(execute: closure)
}
}
并使用
let foo = syncMain {
Foo()
}
但编译器无法识别 if Thread.isMainThread
并再次抛出相同的错误消息,Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
。
正确的做法是什么?我需要某种可以在应用程序启动前初始化的全局变量。
一种方法是将变量存储在容器中(如 enum
作为抽象命名空间)并将其与主要参与者隔离。
@MainActor
enum Globals {
static let foo = Foo()
}
一个同样有效的方法是在对象本身上有一个“类单例”static
属性,它具有相同的目的,但没有额外的对象。
@MainActor
struct Foo {
static let global = Foo()
}
您现在可以通过 Foo.global
访问全局对象。
需要注意的一件事是现在将延迟初始化(在第一次调用时)而不是立即初始化。 但是,您可以通过对该对象进行任何访问来提前强制初始化。
// somewhere early on
_ = Foo.global
Swift 5.5 和 Swift 5.6 中的错误
TL;DR:@MainActor 有时不会在主线程上调用 static let
变量。请改用 static var
。
虽然这可以编译并工作,但似乎这可能会在主线程之外调用初始化程序,然后在初始化程序内部进行任何调用。
@MainActor
struct Bar {
init() {
print("bar init is main", Thread.isMainThread)
}
func barCall() {
print("bar call is main", Thread.isMainThread)
}
}
@MainActor
struct Foo {
@MainActor
static let global = Foo()
init() {
print("foo init is main", Thread.isMainThread)
let b = Bar()
b.barCall()
}
func fooCall() {
print("foo call is main", Thread.isMainThread)
}
}
Task.detached {
await Foo.global.fooCall()
}
// prints:
// foo init is main false
// bar init is main false
// bar call is main false
// foo call is main true
这是一个错误(参见 SR-16009)。
一种解决方法是始终确保初始化发生在 @MainActor
上下文中,我们可以通过在我们第一次调用的地方注释闭包来做到这一点。
Task.detached { @MainActor in
await Foo.global.fooCall()
}
或者,您可以使用 static var
而不是 static let
,因为在这种情况下会强制执行正确的隔离行为。