web-dev-qa-db-ja.com

Swift 3のdispatch_onceはどこにありますか?

さて、Xcode 8の新しい Swifty Dispatch API について知りました。DispatchQueue.main.asyncを使用して楽しんでおり、Dispatchモジュールをブラウジングしていますすべての新しいAPIを見つけるためのXcode。

しかし、私はdispatch_onceを使用して、シングルトン作成や1回限りのセットアップなどが(マルチスレッド環境でも)複数回実行されないようにします...そして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.")
    }
}
49
rickster

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

dispatch_onceを1回初期化で使用していて、何らかの値が得られている場合は、これは素晴らしいことです。初期化するグローバル変数または静的プロパティの値。

しかし、あなたがdispatch_onceを使用して、必ずしも結果をもたらさない仕事をしている場合はどうでしょうか?グローバル変数または静的プロパティでそれを行うことができます:その変数の型をVoidにするだけです:

let justAOneTimeThing: () = {
    print("Not coming back here.")
}()

また、グローバル変数または静的プロパティにアクセスして1回限りの作業を行うのが適切ではない場合、たとえば、クライアントがライブラリを操作する前に「initialize me」関数を呼び出すようにしたい場合は、関数内のアクセス:

func doTheOneTimeThing() {
    justAOneTimeThing
}

詳しくは 移行ガイド をご覧ください。

54
rickster

こことインターウェブの周りの他の答えはかなり素晴らしいですが、私はこの小さな情報も言及されるべきだと感じています:

dispatch_onceの素晴らしい点は、最初の実行後にコードを本質的には理解できないほど最適化することでしたが、(実際の)グローバルを設定してチェックするよりもはるかに高速であると確信していますトークン。

トークンの事柄はSwiftで合理的に実装できますが、さらに別の格納されたブール値を宣言する必要はありません。スレッドが安全でないことは言うまでもありません。 doc にあるように、「遅延初期化グローバル」を使用する必要があります。ええ、でもなぜグローバルな範囲を散らかすのですか?

誰かがより良い方法を私に納得させるまで、私はそれを使用するスコープ内で、またはそれに合理的に近い範囲で、次のように一度だけのクロージャーを宣言する傾向があります。

private lazy var foo: Void = {
    // Do this once
}()

基本的に、「これを読んだとき、fooはこのブロックを実行した結果であるべきだ」と言っています。グローバルlet定数とまったく同じように動作しますが、正しいスコープ内で動作します。そしてきれい。それから、私はそれを他の方法では決して使用されない何かに読みとることによって、好きな場所でそれを呼び出します。私はそのためにSwiftの_が好きです。そのようです:

_ = foo

この本当にクールな癖は実際にはしばらくの間ありますが、あまり愛を見ていない。基本的に、何かがそのVoid結果を見たいと思うまで、呼び出されないクロージャーとして実行時に変数をそのままにします。読み取り時に、クロージャーを呼び出して破棄し、その結果をfooに保持します。 Voidは実質的にメモリ単位では何も使用しないため、後続の読み取り(つまり_ = foo)はCPUで何も実行しません。 (それについては引用しないでください。誰かがアセンブリを確認してください!)好きなだけ持って、Swiftは基本的に最初の実行後にそれを気にしなくなります!古いdispatch_once_tをなくし、クリスマスの日に最初に開いたときと同じくらい多くのコードを保持します。

私の1つの問題は、最初の読み取りの前にfooを他の値に設定すると、コードがneverが呼び出されることです!したがって、それを防ぐグローバルlet定数。つまり、クラススコープの定数はselfでうまく再生されないため、インスタンス変数で再生できません...しかし、真剣に、いつanythingとにかくVoidに??

それに、戻り値の型をVoidまたは()として指定する必要があります。そうしないと、selfについて文句を言うでしょう。誰がサンク?

lazyは、変数を本来のように遅延させるためのものであるため、Swiftはinit()で直接実行しません。

あなたがそれに書かないことを覚えている限り、かなりおしゃれです! :P

14
SeizeTheDay

「lazy var」パターンを使用すると、ディスパッチトークンを気にする必要がなくなり、dispatch_once()よりも便利になりますが、呼び出しサイトでの表示が気に入らなくなります。

_ = doSomethingOnce

このステートメントは関数呼び出しのように見えますが(アクションを意味するため)、まったくそうではありません。また、結果を明示的に破棄するために_ =を記述する必要はありません。

より良い方法があります:

lazy var doSomethingOnce: () -> Void = {
  print("executed once")
  return {}
}()

これにより、次のことが可能になります。

doSomethingOnce()

これはあまり効率的ではないかもしれませんが(Voidを単に破棄するのではなく空のクロージャーを呼び出すため)、改善された明快さは私にとって完全に価値があります。

13

Swift 3.0の「dispatch_once」の例

ステップ1:以下のコードをSingleton.Swift(シングルトンクラス)に置き換えるだけです

// 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"
}
}

シングルトンサンプルイメージ

ステップ2:ViewController.Swiftからシングルトンを呼び出す

// 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)

    }

ViewControllerサンプル画像

このような出力

My Class Initialized
My String
New String
New String
8
Vivek

Xcode 8 GA Swift 3でコンパイル

Dispatch_onceシングルトンクラスインスタンスを作成するための推奨されるエレガントな方法:

final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...

使用するには:

if TheRoot.shared.appState == .normal {
...
}

これらの行は何をしますか?

final-クラスをオーバーライドしたり、拡張したりすることはできないため、コードの実行速度がやや速くなり、間接性が少なくなります。

static let shared = TheRoot()-この行は遅延initを実行し、一度だけ実行されます。

このソリューションはスレッドセーフです。

6
t1ser

移行ガイド

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

例:

  let myGlobal = { … global contains initialization in a call to a closure … }()

  // using myGlobal will invoke the initialization code only the first time it is used.
  _ = myGlobal  
3
scollaco