言語バージョン3での変更後のSwiftのdispatch_once
の新しい構文は何ですか?古いバージョンは次のとおりでした。
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
これらは libdispatchへの変更 行われたものです。
doc から:
ディスパッチ
無料の関数dispatch_onceは、Swiftでは使用できなくなりました。 Swiftでは、遅延初期化されたグローバルまたは静的プロパティを使用して、dispatch_onceが提供するものと同じスレッドセーフおよび1回限りの保証を取得できます。例:
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.
遅延初期化されたグローバルを使用すると、一度の初期化には意味がありますが、他のタイプには意味がありません。シングルトンのようなものに遅延初期化されたグローバルを使用することは非常に理にかなっていますが、スウィズルのセットアップをガードするようなことにはあまり意味がありません。
次に、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()
}
2つのモジュールに同じファイルがある場合、衝突が発生する可能性があると思います。残念です#module
がありません
編集
シンプルなソリューションは
lazy var dispatchOnce : Void = { // or anyName I choose
self.title = "Hello Lazy Guy"
return
}()
のように使用
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = dispatchOnce
}
ブリッジングヘッダーを追加する場合は、引き続き使用できます。
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が提案するものを使用する必要がありますが、2つの関数で1つのトークンでdispatch_once
を使用する必要がある正当な用途があり、代わりにAppleが提供するものではカバーされません。
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
は、macOS SDK 10.12の廃止予定の警告によりos_unfair_lock
に置き換えられました。
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)
}
}
次のようにトップレベル変数関数を宣言できます。
private var doOnce: ()->() = {
/* do some work only once per instance */
return {}
}()
次に、これをどこでも呼び出します:
doOnce()