Swift 3 GCDAPIの変更後のdispatch_once


84

dispatch_once言語バージョン3で変更が加えられた後の、Swiftの新しい構文は何ですか?旧バージョンは以下の通り。

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
    }
}

これらは、行われたlibdispatchへの変更です。



回答stackoverflow.com/a/38311178/1648724stackoverflow.com/a/39983813/1648724に基づいて、これを行うためにCocoaPodを作成しました:pod 'SwiftDispatchOnce', '~> 1.0'乾杯。:]
–JRG-開発者2017

回答:


70

ドキュメントから:

ディスパッチ
無料の関数dispatch_onceはSwiftでは使用できなくなりました。Swiftでは、遅延初期化されたグローバルまたは静的プロパティを使用して、dispatch_onceが提供するのと同じスレッドセーフおよびcall-once保証を取得できます。例:

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.

3
Swiftが急速に変化することを知らなかったわけではなく、Swiftのバージョン間で壊れたコードを修正する必要があります。
Abizern 2016年

2
最大の問題は、Swift3と常に互換性があるとは限らないサードパーティのポッドです。
ティンカーベル2016年

4
これは、サードパーティの依存関係である@Tinkerbellを導入するときに発生する技術的負債です。私はSwiftが大好きですが、まさにこの理由でそれを使用する外部依存関係を持ち込むことに特に注意を払っています。
クリス・ワグナー

17
dispatch_once明確でした。これは、残念ながら..醜いと紛らわしいです
アレクサンドル・G

104

遅延初期化グローバルを使用することは、一度の初期化には意味がありますが、他のタイプには意味がありません。シングルトンのようなものに怠惰な初期化されたグローバルを使用することは非常に理にかなっていますが、スウィズル設定を保護するようなものにはあまり意味がありません。

以下は、dispatch_onceのSwift3スタイルの実装です。

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への移行の時期にあるため、swift2の実装例を次に示します。

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()
    }

}

解決していただきありがとうございます。私はまさにスウィズルのセットアップに閉じ込められていました。迅速なチームがこのユースケースに対処することを願っています。
salman140 2016

2
あなたは絶対に使用すべきではobjc_sync_enterありobjc_sync_exitません。
smat88dd 2017年

1
なんで?
Tod Cunningham

1
パフォーマンスのために、_onceTrackersの配列の代わりにセットを使用する必要があります。これにより、O(N)からO(1)への時間計算量が改善されます。
Werner Altewischer 2017

2
したがって、再利用可能なクラスは、それほど再利用されないと想定して記述します:-)時間計算量をO(N)からO(1)に下げるために追加の作業が必要ない場合は、常にIMHOで行う必要があります。
WernerAltewischer19年

62

上記のTodCunninghamの回答を拡張して、ファイル、関数、および行からトークンを自動的に作成する別のメソッドを追加しました。

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


これにより、何が起こっているのかが明らかになります。ありがとう。
nyxee 2017


本当にありがとう助けた
Manjunath C.Kadani

19

編集

@Frizlabの答え-このソリューションはスレッドセーフであることが保証されていません。これが重要な場合は、代替手段を使用する必要があります

簡単な解決策は

lazy var dispatchOnce : Void  = { // or anyName I choose

    self.title = "Hello Lazy Guy"

    return
}()

のように使用

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}

1
遅延変数宣言は通常のコードとインラインで作成できないため、これはまったく役に立ちません。構造体またはクラスの定義に含める必要があります。つまり、dispatchOnceのコンテンツは、インスタンスの周囲のスコープをキャプチャできません。たとえば、まだ実行されていないクロージャを宣言した場合、そのクロージャ内で構造体を宣言して、レイジー変数の内容を周囲のクロージャから変数をキャプチャする別のクロージャにすることはできません...
CommaToast 2017

3
このコードはdispatch_onceとまったく同じセマンティクスを持っていないため、反対票を投じました。dispatch_onceは、どのスレッドから呼び出しても、コードが1回だけ実行されることを保証します。レイジー変数は、マルチスレッド環境で未定義の動作をします。
Frizlab 2017

このソリューションでは、initブロックが場合によっては2回呼び出されます
神聖な

8

ブリッジヘッダーを追加すると、引き続き使用できます。

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);
}

これでmxcl_dispatch_once、Swiftから使用できるようになります。

ほとんどの場合、代わりにAppleが提案するものを使用する必要がdispatch_onceありますが、2つの関数で単一のトークンを使用する必要があり、Appleが代わりに提供するものでカバーされていない正当な使用法がいくつかありました。


7

次のようにトップレベルの変数関数を宣言できます。

private var doOnce: ()->() = {
    /* do some work only once per instance */
    return {}
}()

次に、これをどこにでも呼び出します。

doOnce()

1
レイジー変数はクラスにスコープされているため、これはdispatch_onceのようには絶対に機能しません。基になるクラスのインスタンスごとに1回実行されます。クラス外に移動し、[プライベートするvar doOnce:() - >()= {}]のいずれか、または静的にマーク[静的プライベートするvar doOnce:() - >()= {}]
イーライ・バーク

1
絶対に正しいです!ありがとう。ほとんどの場合、インスタンスごとに1回のアクションが必要になります。
ボグダンノビコフ2018

2
これは本当に素晴らしい解決策です!エレガント、短く、クリア
ベンレギエロ

6

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日):macOS SDK10.12で非推奨の警告にOSSpinLock置き換えられました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)
   }
}

OSSSpinLockがiOS10.0で非推奨になっているというメッセージが表示されます
markhorrocks 2017

2
ありがとう!サンプルコードが更新されました。OSSpinLockに置き換えられましたos_unfair_lock。ところで:これはについての良いWWDCビデオですConcurrent Programmingdeveloper.apple.com/videos/play/wwdc2016/720
Vlad 2017

0

私は上記の答えを改善して結果を得る:

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()
        }
    }
}

-3

Swift 1.2以降を使用している場合はクラス定数アプローチを使用し、以前のバージョンをサポートする必要がある場合はネストされた構造体アプローチを使用します。Swiftのシングルトンパターンの調査。以下のすべてのアプローチは、遅延初期化とスレッドセーフをサポートします。dispatch_onceアプローチはSwift3.0では機能しません

アプローチA:クラス定数

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

1
Swift 3のソリューションについて具体的に尋ねられた質問
thesummersign 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.