如何在 Swift 中只执行一次代码?

How do I execute code once and only once in Swift?

到目前为止我看到的答案 (1, 2, 3) 建议使用 GCD 的 dispatch_once 因此:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

输出:

This is printed only on the first call to test()
This is printed for each call to test()

但等一下。 token 是一个变量,所以我可以很容易地做到这一点:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

token = 0

test()

输出:

This is printed only on the first call to test()
This is printed for each call to test()
This is printed only on the first call to test()
This is printed for each call to test()

所以dispatch_once是没有用的,如果我们可以改变token的值!将 token 转换为常量并不简单,因为它需要 UnsafeMutablePointer<dispatch_once_t>.

类型

那么我们应该在 Swift 中放弃 dispatch_once 吗?有没有更安全的方法只执行一次代码?

一个人去看医生,说"Doctor, it hurts when I stamp on my foot"。医生回答说,"So stop doing it"。

如果您故意更改您的调度令牌,那么是的 - 您将能够执行代码两次。但是,如果您绕过旨在防止以 any 方式多次执行的逻辑,您将能够做到这一点。 dispatch_once 仍然是确保代码只执行一次的最佳方法,因为它处理所有(非常)复杂的围绕初始化和竞争条件的极端情况,而简单的布尔值无法涵盖这些情况。

如果您担心有人可能会不小心重置令牌,您可以将其包装在一个方法中,并使其尽可能明显地说明后果。像下面这样的东西会将令牌限定在方法中,并防止任何人未经认真努力就更改它:

func willRunOnce() -> () {
    struct TokenContainer {
        static var token : dispatch_once_t = 0
    }

    dispatch_once(&TokenContainer.token) {
        print("This is printed only on the first call")
    }
}

由闭包初始化的静态属性是 运行 惰性的并且最多一次,因此尽管被调用了两次,但它只打印一次:

/*
run like:

    swift once.swift
    swift once.swift run

to see both cases
*/
class Once {
    static let run: Void = {
        print("Behold! \(__FUNCTION__) runs!")
        return ()
    }()
}

if Process.arguments.indexOf("run") != nil {
    let _ = Once.run
    let _ = Once.run
    print("Called twice, but only printed \"Behold\" once, as desired.")
} else {
    print("Note how it's run lazily, so you won't see the \"Behold\" text now.")
}

示例 运行s:

~/W/WhenDoesStaticDefaultRun> swift once.swift
Note how it's run lazily, so you won't see the "Behold" text now.
~/W/WhenDoesStaticDefaultRun> swift once.swift run
Behold! Once runs!
Called twice, but only printed "Behold" once, as desired.

我认为最好的方法是根据需要延迟构建资源。 Swift 让这变得简单。

有几个选项。如前所述,您可以使用闭包在类型中初始化静态 属性。

然而,最简单的选择是定义一个全局变量(或常量)并用闭包对其进行初始化,然后在需要初始化代码的任何地方引用该变量:

let resourceInit : Void = {
  print("doing once...")
  // do something once
}()

另一种选择是将类型包装在函数中,以便在调用时更好地读取。例如:

func doOnce() {
    struct Resource {
        static var resourceInit : Void = {
            print("doing something once...")
        }()
    }

    let _ = Resource.resourceInit
}

您可以根据需要对此进行修改。例如,您可以根据需要使用私有全局和内部或 public 函数,而不是使用函数内部类型。

但是,我认为最好的方法是确定您需要初始化哪些资源并将它们延迟创建为全局或静态属性。