dispatch_once Swift 3 GCD API 变化后
dispatch_once after the Swift 3 GCD API changes
在语言版本 3 中进行更改后,Swift 中 dispatch_once
的新语法是什么?旧版本如下
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
这些 are the changes to libdispatch 制作的。
来自doc:
Dispatch
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. Example:
let myGlobal: () = { … global contains initialization in a call to a closure … }()
_ = myGlobal // using myGlobal will invoke the initialization code only the first time it is used.
虽然使用延迟初始化的全局变量对某些一次性初始化有意义,但对其他类型没有意义。对于像单例这样的事情使用惰性初始化全局变量很有意义,对于像保护 swizzle 设置这样的事情没有多大意义。
这是 dispatch_once 的 Swift 3 样式实现:
public extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block:@noescape(Void)->Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
这是一个用法示例:
DispatchQueue.once(token: "com.vectorform.test") {
print( "Do This Once!" )
}
或使用 UUID
private let _onceToken = NSUUID().uuidString
DispatchQueue.once(token: _onceToken) {
print( "Do This Once!" )
}
由于我们目前正处于从 swift 2 到 3 的过渡期,这里有一个 swift 2 实施示例:
public class Dispatch
{
private static var _onceTokenTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token token: String, @noescape block:dispatch_block_t) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTokenTracker.contains(token) {
return
}
_onceTokenTracker.append(token)
block()
}
}
扩展上面 Tod Cunningham 的回答,我添加了另一种方法,它自动从文件、函数和行生成令牌。
public extension DispatchQueue {
private static var _onceTracker = [String]()
public class func once(
file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void
) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(
token: String,
block: () -> Void
) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
所以可以更简单的调用:
DispatchQueue.once {
setupUI()
}
如果您愿意,您仍然可以指定一个令牌:
DispatchQueue.once(token: "com.hostname.project") {
setupUI()
}
我想如果您在两个模块中有相同的文件,您可能会发生冲突。可惜没有#module
如果添加桥接头,您仍然可以使用它:
typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);
然后在 .m
某处:
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
dispatch_once(predicate, block);
}
您现在应该可以使用 Swift 中的 mxcl_dispatch_once
。
大多数情况下,您应该改用 Apple 建议的内容,但我有一些合法用途需要 dispatch_once
在两个函数中使用单个令牌,而 Apple 提供的内容并未涵盖。
如果您使用 Swift 1.2 或更高版本,请使用 class 常量方法;如果您需要支持早期版本,请使用嵌套结构方法。
Swift 中单例模式的探索。以下所有方法都支持延迟初始化和线程安全。
dispatch_once 方法在 Swift 3.0
中不起作用
方法 A:Class 常量
class SingletonA {
static let sharedInstance = SingletonA()
init() {
println("AAA");
}
}
方法 B:嵌套结构
class SingletonB {
class var sharedInstance: SingletonB {
struct Static {
static let instance: SingletonB = SingletonB()
}
return Static.instance
}
}
方法 C:dispatch_once
class SingletonC {
class var sharedInstance: SingletonC {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: SingletonC? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SingletonC()
}
return Static.instance!
}
}
Swift 3:对于那些喜欢可重复使用的人类(或结构):
public final class /* struct */ DispatchOnce {
private var lock: OSSpinLock = OS_SPINLOCK_INIT
private var isInitialized = false
public /* mutating */ func perform(block: (Void) -> Void) {
OSSpinLockLock(&lock)
if !isInitialized {
block()
isInitialized = true
}
OSSpinLockUnlock(&lock)
}
}
用法:
class MyViewController: UIViewController {
private let /* var */ setUpOnce = DispatchOnce()
override func viewWillAppear() {
super.viewWillAppear()
setUpOnce.perform {
// Do some work here
// ...
}
}
}
更新(2017 年 4 月 28 日):OSSpinLock
替换为 os_unfair_lock
由于 macOS SDK 10.12 中的弃用警告。
public final class /* struct */ DispatchOnce {
private var lock = os_unfair_lock()
private var isInitialized = false
public /* mutating */ func perform(block: (Void) -> Void) {
os_unfair_lock_lock(&lock)
if !isInitialized {
block()
isInitialized = true
}
os_unfair_lock_unlock(&lock)
}
}
编辑
@Frizlab 的回答——这个解决方案不保证是线程安全的。如果这是至关重要的,则应使用替代方案
简单的解决方案是
lazy var dispatchOnce : Void = { // or anyName I choose
self.title = "Hello Lazy Guy"
return
}()
像
一样使用
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = dispatchOnce
}
你可以像这样声明一个顶层变量函数:
private var doOnce: ()->() = {
/* do some work only once per instance */
return {}
}()
然后在任何地方调用它:
doOnce()
我改进以上答案得到结果:
import Foundation
extension DispatchQueue {
private static var _onceTracker = [AnyHashable]()
///only excute once in same file&&func&&line
public class func onceInLocation(file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
///only excute once in same Variable
public class func onceInVariable(variable:NSObject, block: () -> Void){
once(token: variable.rawPointer, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: AnyHashable,block: () -> Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
extension NSObject {
public var rawPointer:UnsafeMutableRawPointer? {
get {
Unmanaged.passUnretained(self).toOpaque()
}
}
}
import UIKit
// dispatch once
class StaticOnceTest {
static let test2 = {
print("Test " + [=10=] + " \()")
}("mediaHSL", 5)
lazy var closure: () = {
test(entryPoint: [=10=], videos: )
}("see all" , 4)
private func test(entryPoint: String, videos: Int) {
print("Test " + entryPoint + " \(videos)")
}
}
print("Test-1")
let a = StaticOnceTest()
a.closure
a.closure
a.closure
a.closure
StaticOnceTest.test2
StaticOnceTest.test2
StaticOnceTest.test2
StaticOnceTest.test2
输出:
Test-1
Test see all 4
Test mediaHSL 5
您可以使用 lazy var closure 并 execute 立即使用 (#arguments_if_needed) 以便它调用只有一次。您可以在闭包内调用任何实例函数 [advantage].
您可以根据需要传递多个参数。您可以在初始化 class 并使用它们时捕获这些参数。
另一种选择:您可以使用 static let 闭包,它只会执行一次,但您不能在该 static let clsource 中调用任何实例 func。 [劣势]
谢谢!
Swift 5
dispatch_once
在 libswiftFoundation.dylib
标准库中仍然可用,它嵌入到任何 swift 应用程序中,因此您可以动态访问导出的符号,获取函数的符号指针,转换和调用:
import Darwin
typealias DispatchOnce = @convention(c) (
_ predicate: UnsafePointer<UInt>?,
_ block: () -> Void
) -> Void
func dispatchOnce(_ predicate: UnsafePointer<UInt>?, _ block: () -> Void) {
let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
if let sym = dlsym(RTLD_DEFAULT, "dispatch_once") {
let f = unsafeBitCast(sym, to: DispatchOnce.self)
f(predicate, block)
}
else {
fatalError("Symbol not found")
}
}
示例:
var token: UInt = 0
for i in 0...10 {
print("iteration: \(i)")
dispatchOnce(&token) {
print("This is printed only on the first call")
}
}
输出:
iteration: 0
This is printed only on the first call
iteration: 1
iteration: 2
iteration: 3
iteration: 4
iteration: 5
iteration: 6
iteration: 7
iteration: 8
iteration: 9
iteration: 10
在语言版本 3 中进行更改后,Swift 中 dispatch_once
的新语法是什么?旧版本如下
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
这些 are the changes to libdispatch 制作的。
来自doc:
Dispatch
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. Example:
let myGlobal: () = { … global contains initialization in a call to a closure … }()
_ = myGlobal // using myGlobal will invoke the initialization code only the first time it is used.
虽然使用延迟初始化的全局变量对某些一次性初始化有意义,但对其他类型没有意义。对于像单例这样的事情使用惰性初始化全局变量很有意义,对于像保护 swizzle 设置这样的事情没有多大意义。
这是 dispatch_once 的 Swift 3 样式实现:
public extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block:@noescape(Void)->Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
这是一个用法示例:
DispatchQueue.once(token: "com.vectorform.test") {
print( "Do This Once!" )
}
或使用 UUID
private let _onceToken = NSUUID().uuidString
DispatchQueue.once(token: _onceToken) {
print( "Do This Once!" )
}
由于我们目前正处于从 swift 2 到 3 的过渡期,这里有一个 swift 2 实施示例:
public class Dispatch
{
private static var _onceTokenTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token token: String, @noescape block:dispatch_block_t) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTokenTracker.contains(token) {
return
}
_onceTokenTracker.append(token)
block()
}
}
扩展上面 Tod Cunningham 的回答,我添加了另一种方法,它自动从文件、函数和行生成令牌。
public extension DispatchQueue {
private static var _onceTracker = [String]()
public class func once(
file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void
) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(
token: String,
block: () -> Void
) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
所以可以更简单的调用:
DispatchQueue.once {
setupUI()
}
如果您愿意,您仍然可以指定一个令牌:
DispatchQueue.once(token: "com.hostname.project") {
setupUI()
}
我想如果您在两个模块中有相同的文件,您可能会发生冲突。可惜没有#module
如果添加桥接头,您仍然可以使用它:
typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);
然后在 .m
某处:
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
dispatch_once(predicate, block);
}
您现在应该可以使用 Swift 中的 mxcl_dispatch_once
。
大多数情况下,您应该改用 Apple 建议的内容,但我有一些合法用途需要 dispatch_once
在两个函数中使用单个令牌,而 Apple 提供的内容并未涵盖。
如果您使用 Swift 1.2 或更高版本,请使用 class 常量方法;如果您需要支持早期版本,请使用嵌套结构方法。 Swift 中单例模式的探索。以下所有方法都支持延迟初始化和线程安全。 dispatch_once 方法在 Swift 3.0
中不起作用方法 A:Class 常量
class SingletonA {
static let sharedInstance = SingletonA()
init() {
println("AAA");
}
}
方法 B:嵌套结构
class SingletonB {
class var sharedInstance: SingletonB {
struct Static {
static let instance: SingletonB = SingletonB()
}
return Static.instance
}
}
方法 C:dispatch_once
class SingletonC {
class var sharedInstance: SingletonC {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: SingletonC? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SingletonC()
}
return Static.instance!
}
}
Swift 3:对于那些喜欢可重复使用的人类(或结构):
public final class /* struct */ DispatchOnce {
private var lock: OSSpinLock = OS_SPINLOCK_INIT
private var isInitialized = false
public /* mutating */ func perform(block: (Void) -> Void) {
OSSpinLockLock(&lock)
if !isInitialized {
block()
isInitialized = true
}
OSSpinLockUnlock(&lock)
}
}
用法:
class MyViewController: UIViewController {
private let /* var */ setUpOnce = DispatchOnce()
override func viewWillAppear() {
super.viewWillAppear()
setUpOnce.perform {
// Do some work here
// ...
}
}
}
更新(2017 年 4 月 28 日):OSSpinLock
替换为 os_unfair_lock
由于 macOS SDK 10.12 中的弃用警告。
public final class /* struct */ DispatchOnce {
private var lock = os_unfair_lock()
private var isInitialized = false
public /* mutating */ func perform(block: (Void) -> Void) {
os_unfair_lock_lock(&lock)
if !isInitialized {
block()
isInitialized = true
}
os_unfair_lock_unlock(&lock)
}
}
编辑
@Frizlab 的回答——这个解决方案不保证是线程安全的。如果这是至关重要的,则应使用替代方案
简单的解决方案是
lazy var dispatchOnce : Void = { // or anyName I choose
self.title = "Hello Lazy Guy"
return
}()
像
一样使用override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = dispatchOnce
}
你可以像这样声明一个顶层变量函数:
private var doOnce: ()->() = {
/* do some work only once per instance */
return {}
}()
然后在任何地方调用它:
doOnce()
我改进以上答案得到结果:
import Foundation
extension DispatchQueue {
private static var _onceTracker = [AnyHashable]()
///only excute once in same file&&func&&line
public class func onceInLocation(file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
///only excute once in same Variable
public class func onceInVariable(variable:NSObject, block: () -> Void){
once(token: variable.rawPointer, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: AnyHashable,block: () -> Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
extension NSObject {
public var rawPointer:UnsafeMutableRawPointer? {
get {
Unmanaged.passUnretained(self).toOpaque()
}
}
}
import UIKit
// dispatch once
class StaticOnceTest {
static let test2 = {
print("Test " + [=10=] + " \()")
}("mediaHSL", 5)
lazy var closure: () = {
test(entryPoint: [=10=], videos: )
}("see all" , 4)
private func test(entryPoint: String, videos: Int) {
print("Test " + entryPoint + " \(videos)")
}
}
print("Test-1")
let a = StaticOnceTest()
a.closure
a.closure
a.closure
a.closure
StaticOnceTest.test2
StaticOnceTest.test2
StaticOnceTest.test2
StaticOnceTest.test2
输出:
Test-1
Test see all 4
Test mediaHSL 5
您可以使用 lazy var closure 并 execute 立即使用 (#arguments_if_needed) 以便它调用只有一次。您可以在闭包内调用任何实例函数 [advantage].
您可以根据需要传递多个参数。您可以在初始化 class 并使用它们时捕获这些参数。
另一种选择:您可以使用 static let 闭包,它只会执行一次,但您不能在该 static let clsource 中调用任何实例 func。 [劣势]
谢谢!
Swift 5
dispatch_once
在 libswiftFoundation.dylib
标准库中仍然可用,它嵌入到任何 swift 应用程序中,因此您可以动态访问导出的符号,获取函数的符号指针,转换和调用:
import Darwin
typealias DispatchOnce = @convention(c) (
_ predicate: UnsafePointer<UInt>?,
_ block: () -> Void
) -> Void
func dispatchOnce(_ predicate: UnsafePointer<UInt>?, _ block: () -> Void) {
let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
if let sym = dlsym(RTLD_DEFAULT, "dispatch_once") {
let f = unsafeBitCast(sym, to: DispatchOnce.self)
f(predicate, block)
}
else {
fatalError("Symbol not found")
}
}
示例:
var token: UInt = 0
for i in 0...10 {
print("iteration: \(i)")
dispatchOnce(&token) {
print("This is printed only on the first call")
}
}
输出:
iteration: 0
This is printed only on the first call
iteration: 1
iteration: 2
iteration: 3
iteration: 4
iteration: 5
iteration: 6
iteration: 7
iteration: 8
iteration: 9
iteration: 10