LoginSignup
27
27

【Swift】@escaping属性のクロージャとは

Last updated at Posted at 2019-11-09

この投稿は何?

Swiftにおけるクロージャには、いくつかの属性を指定できます。
その1つに@escapingがありますが、これについて調査したことを覚書として投稿します。
Swift Language Guideより

Swiftを基礎から学ぶには
自著、工学社より発売中の「まるごと分かるSwiftプログラミング」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

実行環境

  • macOS X 10.15.1 Catalina
  • Xcode 11.2

クロージャをエスケープする

クロージャをエスケープする例

エスケープクロージャのよくある例として、非同期処理をする完了ハンドラとしてのクロージャがあります。
関数に渡されたハンドラは、開始すると再び関数に戻ります。
ただし、そのクロージャがいつ呼び出されるかは決まっておらず、実際にはハンドラ完了後です。
関数の実行が完了した後にクロージャを呼び出すためには、エスケープしておく必要があります。

@escapeing属性を記述する

クロージャは、関数に引数として渡されたとき「関数をエスケープ」できます。
エスケープしたクロージャは、関数に戻った後でも呼び出し可能です。
エスケープクロージャを定義するには、関数の引数宣言でクロージャに@escapingを記述します。
クロージャをエスケープさせるには、関数の外側で定義された変数に割り当てます。

クロージャをエスケープする
// 関数のスコープ外に定義した変数 
var completionHandlers: [() -> Void] = []

// 完了ハンドラとしてエスケープクロージャを受け取る関数
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    // クロージャをスコープ外の変数に割り当てる
    completionHandlers.append(completionHandler)
} 

someFunctionWithEscapingClosure(_:)関数は、クロージャを引数として受け取ります。
受け取ったクロージャはすぐに実行せず、関数の外側で宣言された配列に追加しています。
この関数のパラメータを@escapingでマークしなかった場合、コンパイルエラーが発生します。スコープの外側に割り当てているからです。

一方、次のsomeFunctionWithNonescapingClosure(:)関数は、クロージャを引数として受け取るものの、エスケープしていません。
ボディでは、受け取ったクロージャをその場で実行するだけです。

エスケープしないクロージャを受け取る関数
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

以下のコードでは、someFunctionWithEscapingClosure(_:)はエスケープクロージャを受け取ります。
対照的に、someFunctionWithNonescapingClosure(_:)はエスケープしないクロージャを受け取ります。

インスタンスメソッドから呼び出す
class SomeClass {
    var x = 10
    func doSomething() {
        // エスケープしたクロージャなので、selfを明示してプロパティを参照
        someFunctionWithEscapingClosure { self.x = 100 }
    }
    func doAnotherSomething() {
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

なお、@escapingでクロージャをマークすると、クロージャ内では自身を明示的に参照する必要があることに注意します。
このクラスのインスタンスからメソッドを実行します。

let instance = SomeClass()
instance.doSomething()  // completionHanders配列にハンドラを追加しただけ.
print(instance.x)       // xは10のまま.

completionHandlers.first?() // 追加したハンドラが実行されて xが100になる.
print(instance.x)    // prints 100

completionHandlers配列に追加されたクロージャを呼び出すことで、xの値が100になります。

instance.doAnotherSomething()
print(instance.x)    // prints 200

一方で、エスケープしていないクロージャは、インスタンスメソッド実行時にxの値に200を割り当てています。

こんな風に理解した

「クロージャをエスケープする」とはつまり、__元の関数との繋がりを断ち切ること__という感じに理解できるかもしれません。
「やること(クロージャ)だけを決めておいて、いつやるかは後で決める」と理解しました。

27
27
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
27