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

18

我希望有一种使用@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

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

1个回答

18
一种方法是将变量存储在容器中(比如作为一个抽象命名空间的枚举),并将其隔离到主要的角色中。
@MainActor
enum Globals {
  static var foo = Foo()
}

一种同样有效的方法是在对象本身上拥有一个类似"singleton"的静态属性,它具有相同的功能,但不需要额外的对象。
@MainActor
struct Foo {
  static var shared = Foo()
}

你现在可以通过Foo.global访问全局对象。
需要注意的一点是,现在全局对象将会在第一次调用时进行延迟初始化,而不是立即初始化。 但是,你可以通过对该对象进行任何访问来强制进行早期初始化。
// somewhere early on
_ = Foo.shared

Swift 5.5 - 5.9存在的Bug(在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)
  }
}

Task.detached {
  await Foo.shared.fooCall()
}
// prints:
// foo init is main false
// foo call is main true

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接