ric*_*ter 49 libdispatch grand-central-dispatch swift swift3
好的,所以我在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.")
}
}
Run Code Online (Sandbox Code Playgroud)
ric*_*ter 54
自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")
}
}
Run Code Online (Sandbox Code Playgroud)
如果你一直dispatch_once用于一次性初始化会产生一些价值,那就太好了 - 你可以把这个值当作你正在初始化的全局变量或静态属性.
但是,如果你正在dispatch_once做一些不一定有结果的工作呢?您仍然可以使用全局变量或静态属性执行此操作:只需创建该变量的类型Void:
let justAOneTimeThing: () = {
print("Not coming back here.")
}()
Run Code Online (Sandbox Code Playgroud)
如果访问全局变量或静态属性来执行一次性工作,那对你来说感觉不对 - 比如说,你希望你的客户在你的库工作之前调用"初始化我"功能 - 只需将其包裹起来访问功能:
func doTheOneTimeThing() {
justAOneTimeThing
}
Run Code Online (Sandbox Code Playgroud)
有关更多信息,请参阅迁移指南
Sei*_*Day 14
这里以及互联网周围的其他答案都非常棒,但我觉得这个小小的问题也应该提到:
最重要的dispatch_once是它是如何优化的,基本上是在第一次运行之后以一种我几乎无法理解的方式对代码进行修复,但可以肯定的是,这将比设置和检查(真实)全局令牌快得多.
虽然令牌可以在Swift中合理地实现,但是必须声明另一个存储的布尔值并不是那么好.更不用说线程不安全了.正如文档所说,你应该使用"懒惰的初始化全局".是的,但为什么会混淆全球范围,对吧?
直到有人说服我一个更好的方法,我倾向于在我将使用它或合理地接近它的范围内声明我的一次性闭包,如下所示:
private lazy var foo: Void = {
// Do this once
}()
Run Code Online (Sandbox Code Playgroud)
基本上我说的是"当我读到这个时,foo应该是运行这个块的结果." 它的行为与全局let常量完全相同,只是在正确的范围内.更漂亮.然后我会把它称之为我想要的东西,把它读成一些永远不会被使用的东西.我喜欢斯威夫特的_.像这样:
_ = foo
Run Code Online (Sandbox Code Playgroud)
这个非常酷的怪癖实际上已经有一段时间了,但并没有看到太多的爱.它基本上在运行时单独保留变量,作为一个未调用的闭包,直到某些东西想要看到它的Void结果.在读取时,它调用闭包,抛出它并保持其结果foo.Void实际上几乎没有任何内存使用,因此后续读取(即_ = foo)在CPU上不执行任何操作.(请不要引用我的话,有人请检查装配以确保!)尽可能多地使用,并且Swift在第一次运行后基本上放弃了关心它!失去那么老dispatch_once_t,并保持你的许多代码与圣诞节那天第一次打开时一样漂亮!
我的一个问题是你可以foo在它第一次读取之前设置为其他东西,然后你的代码永远不会被调用!因此全局let常数可以防止这种情况发生.事情是,在类范围常量不很好地发挥self,所以用实例变量没有上场......但严重的是,当你设置什么来Void呢?
这一点,你需要指定返回类型Void或(),否则还是会抱怨self.谁笨蛋?
而且lazy只是让变量变得懒惰,所以Swift不会直接运行它init().
非常时髦,只要你记得不要写它!:P
And*_*nko 14
虽然"lazy var"模式允许我停止关心调度令牌并且通常比dispatch_once()我更方便,但我不喜欢它看起来如何看待呼叫站点:
_ = doSomethingOnce
Run Code Online (Sandbox Code Playgroud)
我希望这个语句看起来更像是一个函数调用(因为它意味着动作),但它看起来并不是那样.此外,必须写入_ =以明确地丢弃结果是不必要和烦人的.
有一个更好的方法:
lazy var doSomethingOnce: () -> Void = {
print("executed once")
return {}
}()
Run Code Online (Sandbox Code Playgroud)
这使得以下可能:
doSomethingOnce()
Run Code Online (Sandbox Code Playgroud)
这可能效率较低(因为它调用空闭包而不是丢弃a Void),但提高清晰度对我来说是完全值得的.
// 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"
}
}
Run Code Online (Sandbox Code Playgroud)
// 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)
}
Run Code Online (Sandbox Code Playgroud)
My Class Initialized
My String
New String
New String
Run Code Online (Sandbox Code Playgroud)
在Xcode 8 GA Swift 3下编译
创建dispatch_once单例类实例的推荐且优雅的方法:
final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...
Run Code Online (Sandbox Code Playgroud)
要使用它:
if TheRoot.shared.appState == .normal {
...
}
Run Code Online (Sandbox Code Playgroud)
这些线做什么?
final - 所以这个类不能被覆盖,扩展,它也使代码运行得更快,间接更少.
static let shared = TheRoot() - 这行执行惰性init,它只运行一次.
此解决方案是线程安全的.