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

Posted

技术标签:

【中文标题】如何在 Swift 中只执行一次代码?【英文标题】:How do I execute code once and only once in Swift? 【发布时间】:2015-12-10 13:51:44 【问题描述】:

到目前为止我看到的答案(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()

所以如果我可以更改token 的值,dispatch_once 将毫无用处!将token 转换为常量并不简单,因为它需要UnsafeMutablePointer<dispatch_once_t> 类型。

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

【问题讨论】:

Objective-C 也有同样的问题。想法是将token 放在与dispatch_once 块相同的范围内(并给它一个更好的名称,例如onceToken,并将其放在dispatch_once 块本身的正上方,以便非常清楚)。跨度> 那么dispatch_once 并不比使用普通的布尔变量更安全。 ***.com/q/25354882/2792531 Eric,关于使用普通 bool 与 dispatch_once 的问题对于 Stack Overflow 来说可能是一个更好的讨论。我很想看到那个答案(如果这里还没有被问及回答)。 最新的答案(如您的参考文献 #1 和 #3 中)确实推荐 GCD,而是一个静态类属性(它以线程安全的方式延迟初始化) . 【参考方案1】:

一个男人去看医生,说“医生,我踩到脚很痛”。医生回答说:“所以别这样了。”

如果您故意更改您的调度令牌,那么可以 - 您将能够执行代码两次。但是,如果您解决旨在防止以任何方式多次执行的逻辑,您将能够做到这一点。 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")
    

【讨论】:

第一行可能就是您所需要的。其余的答案是多余的。 ;) 哈哈。最好的解释。 我根本不同意。 dispatch_once 的文档声明它“在应用程序的生命周期内执行一次且仅一次的块对象”。这里没有歧义,API 违反了合同。而且那个医生不称职。 @Eric:看看 Greg Parker(来自 Apple)***.com/a/19845164/1187415 的这个答案:“dispatch_once() 的实现要求 dispatch_once_t 为零,并且 从未非零。 ..." Greg 补充说:“实例变量被初始化为零,但它们的内存可能先前存储了另一个值。这使得它们对于 dispatch_once() 使用不安全。”说得够多了……【参考方案2】:

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

/*
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/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.

【讨论】:

这很有效。必须将其包装在 Type 中只是有点麻烦,但无论如何这都对应着很多用途。 您不需要将静态属性包装在类型中。您可以声明一个全局变量或常量并使用闭包对其进行初始化。闭包将被懒惰地调用一次。例外的是,如果在 main.swift 中定义了全局变量/常量,它仍然会执行一次,但是按照定义的顺序(即不延迟)。 顺便说一句,这实际上是在生成的代码中使用'dispatch_once' :)。它只是将其隐藏在语言语义中。 @AdamWright:它在 OS X 上使用dispatch_once,在 Linux 下使用pthread_once。将其推送到语言中的好处是它可以防止任何人访问和弄乱令牌跟踪执行状态,这是 OP 的问题。 我讨厌看到这是通过属性而不是方法来完成的 Once.run()【参考方案3】:

我认为最好的方法是根据需要懒惰地构造资源。 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

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

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

【讨论】:

以上是关于如何在 Swift 中只执行一次代码?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 NSTimer 在 swift 中只工作一次

如何在 Vue 中只执行一次方法?

如何在python中只执行一次我的if循环?

unittest系统如何在一个测试类多个测试用例执行中只初始化和清理一次?

如何限制 UITextField 在 Swift 中只接受数字?

如何在while循环中只打印一次帖子?