web-dev-qa-db-ja.com

Swift 3 GCD APIの変更後のdispatch_once

言語バージョン3での変更後のSwiftのdispatch_onceの新しい構文は何ですか?古いバージョンは次のとおりでした。

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

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

75
David

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.
54
Dershowitz123

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

次に、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()
    }

}
91
Tod Cunningham

上記の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がありません

52
VaporwareWolf

編集

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

シンプルなソリューションは

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

    self.title = "Hello Lazy Guy"

    return
}()

のように使用

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

ブリッジングヘッダーを追加する場合は、引き続き使用できます。

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が提供するものではカバーされません。

8
mxcl

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)
   }
}
5
Vlad

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

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

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

doOnce()
4
Bogdan Novikov