Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

この投稿は何?

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

実行環境

  • 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を割り当てています。

こんな風に理解した

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

chino_tweet
小学校で放課後プログラミング教室を実施しています。 学生や社会人向けも。 初学者のためのわかりやすい教材を目指しています。
https://www.facebook.com/ichihara.programming/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away