dispatch_once 在 Swift 3 中去哪里?

Whither dispatch_once in Swift 3?

好的,所以我在 Xcode 8 中发现了新的 。我使用 DispatchQueue.main.async 很开心,我一直在浏览 [=14] =] 模块在 Xcode 中查找所有新 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.")
    }
}

自Swift1.x以来,Swift一直在使用dispatch_oncebehind the scenes对全局变量和静态属性进行线程安全的惰性初始化。

所以上面的 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.")
}()

并且如果访问全局变量或静态 属性 来执行一次性工作对您来说感觉不合适 - 比如说,您希望您的客户在之前调用 "initialize me" 函数他们与您的图书馆合作——只需将访问权包装在一个函数中:

func doTheOneTimeThing() {
    justAOneTimeThing
}

有关更多信息,请参阅 migration guide

编译在Xcode 8 GA Swift 3

创建 dispatch_once 单例 class 实例的推荐优雅方式:

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

使用方法:

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

这些行是做什么的?

final - 所以 class 不能被覆盖、扩展,它也使代码比 运行 更快一些,间接寻址更少。

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

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

根据 Migration Guide:

The free function dispatch_once is no longer available in Swift. In Swift, you can use lazily initialized globals or static properties and get the same thread-safety and called-once guarantees as dispatch_once provided.

示例:

  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  

这里和互联网上的其他答案都很好,但我觉得还应该提到这个小花絮:

dispatch_once 的伟大之处在于它的优化程度,本质上是在第一个 运行 之后以一种我几乎无法理解的方式修改代码,但我有理由相信那样会更快而不是设置和检查(真实的)全局令牌。

虽然可以在 Swift 中合理地实现令牌的东西,但必须声明另一个存储的布尔值并不是那么好。更不用说线程不安全了。正如 doc 所说,您应该使用 "lazily initialized global." 是的,但为什么要弄乱全局范围,对吧?

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

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

基本上我是说 "When I read this, foo should be the result of running this block." 它的行为方式与全局 let 常量完全相同,只是在正确的范围内。而且更漂亮。然后我会在任何我喜欢的地方调用它,把它读成永远不会被用到的东西。为此,我喜欢 Swift 的 _。像这样:

_ = foo

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

我的一个问题是,您可以在首次读取之前将 foo 设置为其他内容,然后您的代码将 永远不会 被调用!因此,全局 let 常量可以防止这种情况发生。事情是,class 范围内的常量不能很好地与 self 一起玩,所以不要玩实例变量......但是说真的,你什么时候设置 anything还是 Void ??

那个,你需要将 return 类型指定为 Void(),否则它仍然会抱怨 self。谁打的?

lazy 只是为了使变量尽可能地惰性化,所以 Swift 不会 运行 它直接在 init() 上运行。

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

Swift 3.0

中“dispatch_once”的示例

第 1 步:只需将下面的代码替换为您的 Singleton.swift(单例 class)

// 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"
}
}

Singleton Sample Image

第 2 步:从 ViewController.swift

调用单例
// 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 Sample Image

这样输出

My Class Initialized
My String
New String
New String

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

_ = doSomethingOnce

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

有更好的方法:

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

这使得以下成为可能:

doSomethingOnce()

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

Thread-safe dispatch_once:

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

    // Do this once
}()