web-dev-qa-db-ja.com

Swift 1.2の@noescape属性

Swift 1.2には関数内のクロージャーパラメーターを持つ新しい属性があり、ドキュメントには次のように記載されています。

これは、パラメーターが呼び出される(または呼び出しで@ noescapeパラメーターとして渡される)だけであることを示します。つまり、呼び出しの存続期間を超えて存続することはできません。

私の理解では、その前に、[weak self]クロージャに強い参照を持たせないようにする。そのクラス、およびselfはnilまたはクロージャーが実行されるときのインスタンスですが、今は@noescapeは、クラスが初期化されない場合、クロージャーが実行されないことを意味します。私はそれを正しく理解していますか?

そして、私が正しいなら、なぜ@noescapeクロージャは、非常によく似た振る舞いをするとき、通常の関数の代わりになりますか?

75
Dániel Nagy

@noescapeは次のように使用できます。

func doIt(code: @noescape () -> ()) {
    /* what we CAN */

    // just call it
    code()
    // pass it to another function as another `@noescape` parameter
    doItMore(code)
    // capture it in another `@noescape` closure
    doItMore {
        code()
    }

    /* what we CANNOT do *****

    // pass it as a non-`@noescape` parameter
    dispatch_async(dispatch_get_main_queue(), code)
    // store it
    let _code:() -> () = code
    // capture it in another non-`@noescape` closure
    let __code = { code() }

    */
}

func doItMore(code: @noescape () -> ()) {}

@noescapeを追加すると、クロージャがどこかに格納されたり、後で使用されたり、非同期で使用されたりすることがなくなります。

呼び出し元の観点からは、呼び出された関数内で使用されるかまったく使用されないため、キャプチャされた変数の寿命を気にする必要はありません。ボーナスとして、暗黙のselfを使用して、self.を入力する手間を省くことができます。

func doIt(code: @noescape () -> ()) {
    code()
}

class Bar {
    var i = 0
    func some() {
        doIt {
            println(i)
            //      ^ we don't need `self.` anymore!
        }
    }
}

let bar = Bar()
bar.some() // -> outputs 0

また、コンパイラーの観点から( リリースノート で文書化されているように):

これにより、いくつかのマイナーなパフォーマンスの最適化が可能になります。

143
rintaro

それについて考える1つの方法は、@ noescapeブロック内のすべての変数が(自己だけでなく)Strongである必要はないということです。

また、変数が割り当てられてブロックにラップされると、関数の最後に通常のように割り当てを解除することはできないため、最適化も可能です。そのため、ヒープに割り当て、ARCを使用して分解する必要があります。 Objective-Cでは、「__ block」キーワードを使用して、変数がブロックフレンドリーな方法で作成されるようにする必要があります。 Swiftはそれを自動的に検出するため、キーワードは不要ですが、コストは同じです。

変数が@nosecapeブロックに渡される場合、それらはスタック変数であり、ARCを解放する必要はありません。

これらの変数は、ブロックの寿命の間「生きている」ことが保証されるため、ゼロ参照の弱い変数(安全でないポインターよりも高価)である必要さえありません。

これらはすべて、より高速で最適なコードをもたらします。また、@ autoclosureブロックを使用するためのオーバーヘッドを削減します(これは非常に便利です)。

28
Michael Gray

(上記のMichael Grayの回答を参照。)

これがSwift向けに具体的に文書化されているかどうか、またはSwiftコンパイラーがそれを最大限に活用しているかどうかはわかりません。しかし、コンパイラーが知っている場合、呼び出される関数は、そのインスタンスへのポインタをヒープに保存しようとはせず、関数が保存しようとするとコンパイル時エラーを発行します。

これは、非スカラー値型(enum、struct、closureなど)を渡すときに特に有益です。それらをコピーすると、単にスタックにポインターを渡すよりもはるかに高価になる可能性があるためです。インスタンスの割り当ても大幅に安価です(1つの命令とmalloc()の呼び出し)。したがって、コンパイラーがこの最適化を行うことができれば、ダブルウィンになります。

繰り返しますが、Swiftコンパイラの実際のバージョンが指定されているかどうかは、Swiftチームによって記述される必要があります。 「マイナー最適化」に関する上記の引用から、そうでないか、Swiftチームはそれを「マイナー」とみなしているように思えます。それは重要な最適化です。

おそらく属性はそこにあるので、(少なくとも将来)コンパイラーはこの最適化を実行できます。

8
David Goodine