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

如何在Swift中执行代码一次且仅执行一次?,swift,grand-central-dispatch,Swift,Grand Central Dispatch,到目前为止,我看到的答案(,)建议使用GCD的dispatch\u 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()") } tes

到目前为止,我看到的答案(,)建议使用GCD的
dispatch\u 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()
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()
但是等一下
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()
This is printed only on the first call to test()
This is printed for each call to test()
因此,如果我可以更改
令牌的值,那么
调度\u once
是没有用的!而且将
标记
转换为常量并不简单,因为它需要将
类型转换为unsafemeutablepointer


那么我们是否应该放弃Swift中的
dispatch\u once
?有没有更安全的方法只执行一次代码

一个男人去看医生,说:“医生,我踩脚的时候疼。”。医生回答说:“所以不要再做了。”

如果您故意更改分派令牌,那么是的-您将能够执行代码两次。但是,如果您使用设计用于以任何方式防止多次执行的逻辑,您将能够做到这一点
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.")
}
示例运行:

~/W/whendostaticdefaultrun>swift once.swift
注意它是如何懒洋洋地运行的,这样您现在就看不到“瞧”文本了。
~/W/whendostaticdefaultrun>swift once.swift run
看到一次跑步!
调用两次,但根据需要仅打印“查看”一次。

我认为最好的方法是根据需要惰性地构造资源。斯威夫特使这变得容易

有几种选择。如前所述,可以使用闭包初始化类型内的静态属性

但是,最简单的方法是定义一个全局变量(或常量),并使用闭包对其进行初始化,然后在需要初始化代码发生一次的任何位置引用该变量:

let resourceInit : Void = {
  print("doing once...")
  // do something once
}()
另一个选项是将类型包装在函数中,以便在调用时读取效果更好。例如:

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

    let _ = Resource.resourceInit
}
您可以根据需要对此进行更改。例如,您可以根据需要使用私有全局函数、内部函数或公共函数,而不是使用函数的内部类型


但是,我认为最好的方法就是确定初始化所需的资源,并将它们作为全局或静态属性惰性地创建。

Objective-C也有同样的问题。我们的想法是将
标记
放在与
dispatch\u once
块相同的范围内(并给它一个更好的名称,如
onceToken
,然后将它放在
dispatch\u once
块本身的正上方,这样它就非常清楚了)。那么
dispatch\u once
并不比使用普通布尔变量更安全,关于使用普通bool与
dispatch\u once
的问题可能是关于堆栈溢出的更好的讨论。我很想看到这个答案(如果这里还没有被问到和回答的话)。最新的答案(如您的参考文献#1和#3)并不推荐GCD,而是一个静态类属性(它是以线程安全的方式缓慢初始化的)。仅第一行就可能是您所需要的。答案的其余部分是多余的哈哈。最好的解释。我根本不同意。
dispatch\u once
的文档说明它“在应用程序的生命周期内只执行一次块对象”。这里没有歧义,API正在破坏合同。埃里克:看看格雷格·帕克(来自苹果公司)的回答:“dispatch_once()的实现要求dispatch_once t为零,而且从来没有非零过……”格雷格补充道:实例变量被初始化为零,但它们的内存以前可能存储了另一个值。这使得它们不安全,无法一次性使用。“说得够多了……这确实有效。将它包装成一个类型有点麻烦,但这与许多用途相对应。你不需要将静态属性包装成一个类型。您可以声明一个全局变量或常量,并用闭包初始化它。关闭将被延迟调用一次。例外情况是,如果在main.swift中定义了全局变量/常量,则仍将按定义顺序执行一次(即不延迟执行)。另外,这实际上是在生成的代码中使用“dispatch_once:)。它只是把它隐藏在语言语义中。@AdamWright:它在OSX上使用
dispatch\u once
,在Linux下使用
pthread\u once
。将它推到语言中的好处是,它可以防止任何人访问和干扰令牌跟踪执行状态,这是OP的问题。我讨厌看到这是通过属性而不是方法
one.run()