初めに
Swift文法を勉強してる中で、クロージャの@escaping
をクリティカルに理解するのに時間がかかったのでこの記事を書いてます。
この記事が非同期処理のクロージャを理解しようとしている人の助けになれば幸いです。
この記事の対象読者
Swiftをある程度触ったことがある
クロージャについてざっくり理解してる
非同期処理での@escaping
の理解がぼんやりしている
そもそも@escaping
とは
@escaping
(escaping属性といいます)は本来クロージャの属性であり、関数や別のクロージャの引数として利用する時のみ利用可能です。
escaping属性は関数に引数として渡されたクロージャが、スコープの外で保持される可能性があることを示す属性で、クロージャが関数のスコープ外で保持される可能性がある場合は必ずつける必要があります。
var closurequeue = [() -> Void]()
func addQueue(operation: @escaping: () -> Void) {
closurequeue.append(operation)
}
//配列にクロージャを追加
addQueue{print("banana")}
addQueue{print("apple")}
//配列内の全ての要素を実行
closurequeue.forEach { $0()}
//実行結果
//banana
//apple
上記のサンプルコードでは、関数内に引数として渡されたクロージャoperationをあらかじめ宣言された配列の変数closurequeue
に代入しています。
その後関数を2度呼びだし、配列に要素を追加してから最後に配列内の要素を全て実行しています。
つまり、関数内で引数として用いたクロージャを関数のスコープ外で用いるときに、@escaping
をつけないといけないということです。
実際上記のプログラムで@escaping
を外すとエラーが出ます。
ここまでの説明を読んで、私自身とてもなるほど!と思いました。
「そっかー関数の引数として用いられたクロージャを関数の外部で使う時は@escaping
をつけないといけないんだ!」
こんな感じで理解してその後も学習を続けていきました。
そしてその理解のままAPI周りの学習をしているときにとあるコードと出会いました。
それがAPI通信で用いられるdataTaask(with:completionHandler:)関数でした。
dataTask関数の引数の@escaping
の理解で詰まった話
dataTaask(with:completionHandler:)関数をapple公式のドキュメントで調べてみると以下のように定義されていました。
func dataTask(
with request: URLRequest,
completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> URLSessionDataTask
注目してほしいのが、引数のクロージャに@escaping
がついてる、つまりこのクロージャは@escaping属性
であるということです。
ここで私は一旦頭の中に100個ぐらい?が浮かびました。
「なんでこの関数の引数のクロージャは@escaping属性
なんだろう🤔
@escaping
をつけるのは関数の引数として用いられたクロージャを関数の外部で使う時なんじゃないの?🤔」
上記の問題をどうやって理解したか
先ほどから何度も用いている
@escaping
をつけるのは関数の引数として用いられたクロージャを関数の外部で使う時
という理解の解釈を少し広げ、関数の引数として用いられたクロージャが関数の実行が終わった後も必要な場合と理解しました。
つまりそもそもdataTask関数
が呼ばれるのと同時に引数のcompletionHandler
が呼ばれるわけではない(これはdataTask関数はデータ通信を行うため、非同期で実行されるから)ため、@escaping
が必要ということです。
よりわかりやすく説明すると、dataTask関数が呼ばれたとき(正確にはdataTask関数によって作成されたタスクが実行された時ですが)、同時にcompletionHandlerクロージャ
が呼ばれるわけではないということです。
実行の順番としては、以下の通りになります。
dataTask関数が呼ばれる(実行される)
↓
非同期で通信処理が行われ、データなどのレスポンスが返ってくる
↓
completionHandlerクロージャの引数(Data?型, URLResponse?型 , Error?型)
に先ほど返ってきたレスポンスの値が入る
↓
completionHandlerクロージャが呼ばれる
という流れになります。つまりわかりやすくまとめると、次のとおりです。
dataTask関数は非同期で行われる処理のため、関数が呼ばれると同時にクロージャも呼ばれるわけではなく、クロージャは非同期でクロージャの引数が返ってきて、セットされてから実行される。つまりこのクロージャは関数の実行と非同期で処理されるため、関数の処理が終わっても使用される可能性がある。そのため関数の実行後も保持しておく必要があるから@escaping
をつける
まとめ
少し長くなってしまいましたが、要は
「非同期処理は関数が呼ばれて、クロージャの引数が用意されてから実行されるから関数の実行と同時じゃないため@escaping
をつけといてあげないといけない!」
と理解しました。
今後も自分がひっかかったところをどんどん記事にしていこうと思います!
最後まで読んでいただきありがとうございました。