如何使用 @MainActor 初始化全局变量?

Wil*_*ler 20 swift swift-concurrency

我想要某种使用 同步的全局变量@MainActor

这是一个示例结构:

@MainActor
struct Foo {}
Run Code Online (Sandbox Code Playgroud)

我想要一个像这样的全局变量:

let foo = Foo()
Run Code Online (Sandbox Code Playgroud)

但是,这不会编译并出现错误Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context

很公平。我尝试在主线程上构建它,如下所示:

let foo = Foo()
Run Code Online (Sandbox Code Playgroud)

这可以编译!但是,它会崩溃EXC_BAD_INSTRUCTION,因为DispatchQueue.main.sync无法在主线程上运行。

我还尝试创建一个包装函数,例如:

let foo = DispatchQueue.main.sync {
    Foo()
}
Run Code Online (Sandbox Code Playgroud)

并使用

let foo = syncMain {
    Foo()
}
Run Code Online (Sandbox Code Playgroud)

但编译器无法识别if Thread.isMainThread并再次抛出相同的错误消息Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context

这样做的正确方法是什么?我需要某种可以在应用程序启动之前初始化的全局变量。

Bra*_*key 19

一种方法是将变量存储在容器中(就像充当enum抽象命名空间)并将其与主要参与者隔离。

@MainActor
enum Globals {
  static var foo = Foo()
}
Run Code Online (Sandbox Code Playgroud)

一种同样有效的方法是static在对象本身上具有“类单例”属性,该属性具有相同的目的,但没有附加对象。

@MainActor
struct Foo {
  static var shared = Foo()
}
Run Code Online (Sandbox Code Playgroud)

您现在可以通过 访问全局对象Foo.global

需要注意的一件事是,现在将延迟初始化(在第一次调用时)而不是立即初始化。但是,您可以通过对对象进行任何访问来强制尽早进行初始化。

// somewhere early on
_ = Foo.shared
Run Code Online (Sandbox Code Playgroud)

Swift 5.5 - 5.9 中的错误(在 5.10 中修复)

@MainActor不会static let在主线程上初始化变量。static var代替使用。

@MainActor
struct Foo {
  static let shared = Foo()
    
  init() {
    print("foo init is main", Thread.isMainThread)
  }
    
  func fooCall() {
    print("foo call is main", Thread.isMainThread)
  }
}
Run Code Online (Sandbox Code Playgroud)
Task.detached {
  await Foo.shared.fooCall()
}
// prints:
// foo init is main false
// foo call is main true
Run Code Online (Sandbox Code Playgroud)

这是一个错误(请参阅问题58270),已在 Swift 5.10 中修复