Swift 3中的dispatch_once是哪一个?

时间:2016-06-14 01:02:50

标签: swift grand-central-dispatch swift3 libdispatch

好的,所以我在Xcode 8中发现了新的Swifty Dispatch API。我正在使用DispatchQueue.main.async玩得开心,而且我一直在浏览Xcode中的Dispatch模块找到所有新的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.")
    }
}

7 个答案:

答案 0 :(得分:54)

自Swift 1.x以来,Swift一直使用dispatch_once behind 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.")
}()

如果访问全局变量或静态属性来执行一次性工作,那对您来说感觉不对 - 比如说,您希望您的客户在使用您的库之前调用“初始化我”功能 - 只需在函数中包含该访问权限:

func doTheOneTimeThing() {
    justAOneTimeThing
}

有关详情,请参阅migration guide

答案 1 :(得分:14)

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

关于dispatch_once的好处是它是如何优化的,基本上是在第一次运行之后以我难以理解的方式修改代码,但我可以肯定地确保它比设置和检查更快(真实的全球代币。

虽然令牌事物可以在Swift中合理地实现,但是必须声明另一个存储的布尔值并不是那么好。更不用说线程不安全了。正如doc所说,你应该使用“懒惰的初始化全局”。是的,但为什么会混淆全球范围,对吧?

直到有人说服我一个更好的方法,我倾向于在我将使用它或合理地接近它的范围内声明我的一次性闭包,如下所示:

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 ??

那,你需要将返回类型指定为Void(),否则它仍然会抱怨self。谁笨蛋?

lazy只是让变量变得懒惰,因此Swift不会在init()上直接运行它。

非常时髦,只要你记得不要写它! :P

答案 2 :(得分:11)

虽然“lazy var”模式允许我停止关注调度令牌并且通常比dispatch_once()更方便,但我不喜欢它在呼叫站点上的看法:

_ = doSomethingOnce

我希望这个语句看起来更像是一个函数调用(因为它意味着动作),但它看起来并不是那样。此外,必须编写_ =来明确地丢弃结果是不必要的并且很烦人。

有一种更好的方法:

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

这使得以下成为可能:

doSomethingOnce()

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

答案 3 :(得分:7)

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

Singleton Sample Image

第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 Sample Image

像这样输出

My Class Initialized
My String
New String
New String

答案 4 :(得分:6)

根据Xcode 8 GA Swift 3进行编译

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

public boolean equals(Foo b){
    return Objects.equals(this.bar, b.bar) &&
        Objects.equals(this.baz, b.baz) && 
        Objects.equals(this.qux, b.qux);
}

使用它:

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

这些线做了什么?

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

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

此解决方案是线程安全的。

答案 5 :(得分:3)

根据Migration Guide

  

免费功能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  

答案 6 :(得分:-3)

线程安全dispatch_once:

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawerLayout;
    private FragmentManager fragmentManager;
    MyApplication MyApp;
    NavigationView navigationView;
    AdView mAdView;
    Toolbar toolbar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {

     **startActivity(new Intent(this, updater.DownloadNewVersion.class));
        super.onCreate(savedInstanceState);**