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

lrl1mhuk  于 2023-01-04  发布在  Swift
关注(0)|答案(4)|浏览(540)

到目前为止,我看到的答案(1,23)建议使用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吗?有没有更安全的方法只执行一次代码?

nimxete2

nimxete21#

一个人去看医生,说:“医生,我踩脚的时候很疼。”医生回答说:“那就别踩了。”
如果你故意改变你的调度令牌,那么是的--你将能够执行代码两次。但是如果你围绕着设计来防止以 * 任何 * 方式多次执行的逻辑工作,你将能够做到这一点。dispatch_once仍然是确保代码只执行一次的最佳方法。因为它可以处理简单布尔值无法处理的初始化和竞态条件周围的所有(非常)复杂的极端情况。
如果你担心有人会不小心重置令牌,你可以把它 Package 在一个方法中,并使它尽可能明显地表现出后果。类似下面的内容将令牌的范围限定在方法中,并防止任何人不费吹灰之力就更改它:

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

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

bakd9h0s2#

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

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

ztigrdn83#

我认为最好的方法是根据需要懒惰地构造资源。Swift使这变得容易。
有几个选项。如前所述,可以使用闭包初始化类型中的静态属性。
然而,最简单的选择是定义一个全局变量(或常量),用闭包初始化它,然后在初始化代码需要发生一次的任何地方引用该变量:

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

另一种选择是将类型 Package 在函数中,以便在调用时读起来更好。例如:

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

    let _ = Resource.resourceInit
}

您可以根据需要对此进行修改。例如,您可以根据需要使用私有全局函数和内部或公共函数,而不是使用函数的内部类型。
但是,我认为最好的方法只是确定您需要初始化哪些资源,然后将它们作为全局或静态属性惰性地创建。

fkaflof6

fkaflof64#

对于任何在这条线上跌跌撞撞的人...我们在Thumbtack遇到了类似的情况,想出了这个:https://www.github.com/thumbtack/Swift-RunOnce。实际上,它允许您编写以下内容

func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated: Bool)
  
    runOnce {
      // One-time code
    }
}

我还写了一个blog post来解释代码是如何工作的,以及为什么我们觉得值得将其添加到我们的代码库中。

相关问题