首页 > 解决方案 > 如何使用@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.

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

标签: swiftswift-concurrency

解决方案


一种方法是将变量存储在容器中(就像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 不会在主线程上调用某些东西。这对您来说可能是也可能不是问题。

虽然这可以编译并工作,但似乎这可能会从主线程调用初始化程序,随后在初始化程序内部进行任何调用。

@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-15227)。

一种解决方法是始终确保初始化发生在@MainActor上下文中,我们可以通过在我们第一次调用的地方注释闭包来做到这一点。

Task.detached { @MainActor in
    await Foo.global.fooCall()
}

推荐阅读