56

好的,所以我在 Xcode 8 中发现了新的Swifty Dispatch API。我玩得很开心DispatchQueue.main.async,我一直在浏览DispatchXcode 中的模块以找到所有新的 API。

但我也dispatch_once用来确保诸如单例创建和一次性设置之类的事情不会多次执行(即使在多线程环境中)......并且dispatch_once在新的 Dispatch 模块中找不到?

static var token: dispatch_once_t = 0
func whatDoYouHear() {
    print("All of this has happened before, and all of it will happen again.")
    dispatch_once(&token) {
        print("Except this part.")
    }
}
4

7 回答 7

58

从 Swift 1.x 开始,Swift 一直在dispatch_once 后台使用线程安全的延迟初始化全局变量和静态属性。

所以static var上面已经在使用dispatch_once,这使得它有点奇怪(并且可能再次将它用作另一个 的标记dispatch_once。事实上,dispatch_once如果没有这种递归,真的没有安全的使用方法,所以他们摆脱了它。取而代之的是,只需使用其上构建的语言功能:

// global constant: SomeClass initializer gets called lazily, only on first use
let foo = SomeClass()

// global var, same thing happens here
// even though the "initializer" is an immediately invoked closure
var bar: SomeClass = {
    let b = SomeClass()
    b.someProperty = "whatever"
    b.doSomeStuff()
    return b
}()

// ditto for static properties in classes/structures/enums
class MyClass {
    static let singleton = MyClass()
    init() {
        print("foo")
    }
}

因此,如果您一直使用dispatch_once一次性初始化来产生某个值,那就太好了——您可以将该值设为您正在初始化的全局变量或静态属性。

但是,如果你dispatch_once用来做不一定有结果的工作怎么办?您仍然可以使用全局变量或静态属性来做到这一点:只需将该变量的类型设为Void

let justAOneTimeThing: () = {
    print("Not coming back here.")
}()

如果访问一个全局变量或静态属性来执行一次性工作对你来说感觉不对——比如说,你希望你的客户在他们使用你的库之前调用一个“初始化我”函数——只需包装它在函数中访问:

func doTheOneTimeThing() {
    justAOneTimeThing
}

有关更多信息,请参阅迁移指南

于 2016-06-14T01:02:49.890 回答
19

虽然“lazy var”模式让我不再关心调度令牌并且通常比dispatch_once()以前更方便,但我不喜欢它在调用站点上的样子:

_ = doSomethingOnce

我希望这个语句看起来更像是一个函数调用(因为它意味着动作),但它看起来根本不是这样。此外,必须写入_ =显式丢弃结果很烦人。

有一个更好的方法:

lazy var doSomethingOnce: () -> Void = {
  print("executed once")
  return {}
}()

这使得以下成为可能:

doSomethingOnce()

这可能效率较低(因为它调用空闭包而不是仅仅丢弃 a Void),但提高清晰度对我来说完全值得。

于 2017-03-07T16:52:34.083 回答
17

这里和互联网周围的其他答案非常好,但我觉得这个小花絮也应该被提及:

最棒的dispatch_once是它的优化程度,基本上是在第一次运行后以一种我几乎不理解的方式修改代码,但可以合理地保证这比设置和检查(真正的)全局令牌要快得多。

虽然令牌可以在 Swift 中合理地实现,但必须声明另一个存储的布尔值并不是那么好。更不用说线程不安全了。正如文档所说,您应该使用“延迟初始化的全局”。是的,但是为什么要把全局范围弄得乱七八糟,对吧?

在有人说服我有更好的方法之前,我倾向于在我将使用它的范围内或相当接近的范围内声明我的 do-once 闭包,如下所示:

private lazy var foo: Void = {
    // Do this once
}()

基本上我是说“当我读到这个时,foo应该是运行这个块的结果。” 它的行为方式与全局常量完全相同let,只是在正确的范围内。而且更漂亮。然后我会在任何我喜欢的地方调用它,通过将它读入其他永远不会使用的东西。我喜欢 Swift 的_。像这样:

_ = foo

这个非常酷的怪癖实际上已经存在了一段时间,但还没有看到太多的爱。它基本上在运行时将变量单独保留,作为未调用的闭包,直到有东西想看到它的Void结果。在读取时,它调用闭包,将其丢弃并将其结果保存在foo. Void在内存方面几乎不使用任何内容,因此后续读取(即_ = foo)在 CPU 上什么也不做。(不要引用我的话,请有人检查一下程序集以确保!)拥有尽可能多的数量,Swift 在第一次运行后基本上不再关心它!丢掉那个旧的dispatch_once_t,并让你的很多代码像圣诞节那天第一次打开它时一样漂亮!

我的一个问题是您可以foo在第一次阅读之前设置其他内容,然后您的代码将永远不会被调用!因此,全局let常数可以防止这种情况发生。问题是,类范围内的常量不能很好self.Void

那,你需要将返回类型指定为Voidor (),否则它仍然会抱怨self. 谁来敲门?

只是为了让变量尽可能地懒惰,所以lazySwift 不会直接在init().

很时髦,只要你记得不要给它写信!:P

于 2016-09-16T22:52:31.037 回答
8

Swift 3.0 中“dispatch_once”的示例

第 1 步:只需将以下代码替换为您的 Singleton.swift(Singleton 类)

// Singleton Class
class Singleton: NSObject { 
var strSample = NSString()

static let sharedInstance:Singleton = {
    let instance = Singleton ()
    return instance
} ()

// MARK: Init
 override init() {
    print("My Class Initialized")
    // initialized with variable or property
    strSample = "My String"
}
}

单例样本图像

第 2 步:从 ViewController.swift 调用 Singleton

// ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let mySingleton = Singleton.sharedInstance
        print(mySingleton.strSample)

        mySingleton.strSample = "New String"
        
        print(mySingleton.strSample)
        
        let mySingleton1 = Singleton.sharedInstance
        print(mySingleton1.strSample)

    }

ViewController 示例图像

像这样输出

My Class Initialized
My String
New String
New String
于 2017-01-05T08:18:52.773 回答
6

在 Xcode 8 GA Swift 3 下编译

创建 dispatch_once 单例类实例的推荐和优雅的方法:

final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...

要使用它:

if TheRoot.shared.appState == .normal {
...
}

这些线有什么作用?

final - 所以类不能被覆盖,扩展,它也使代码运行得更快,间接更少。

static let shared = TheRoot() - 这一行执行惰性初始化,并且只运行一次。

这个解决方案是线程安全的。

于 2016-08-22T15:01:31.697 回答
4

根据迁移指南

自由函数 dispatch_once 在 Swift 中不再可用。在 Swift 中,您可以使用延迟初始化的全局变量或静态属性,并获得与 dispatch_once 提供的相同的线程安全和调用一次保证。

例子:

  let myGlobal = { … global contains initialization in a call to a closure … }()

  // using myGlobal will invoke the initialization code only the first time it is used.
  _ = myGlobal  
于 2016-08-22T18:38:05.863 回答
-3

线程安全的 dispatch_once:

private lazy var foo: Void = {
    objc_sync_enter(self)
    defer { objc_sync_exit(self) }

    // Do this once
}()
于 2018-04-10T18:38:52.827 回答