LoginSignup
0
1

[Swift]非同期処理における@escapingの忘備録

Last updated at Posted at 2023-12-02

初めに

Swift文法を勉強してる中で、クロージャの@escapingをクリティカルに理解するのに時間がかかったのでこの記事を書いてます。
この記事が非同期処理のクロージャを理解しようとしている人の助けになれば幸いです。

この記事の対象読者

Swiftをある程度触ったことがある
クロージャについてざっくり理解してる
非同期処理での@escapingの理解がぼんやりしている

そもそも@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公式のドキュメントで調べてみると以下のように定義されていました。

dataTask関数の定義
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をつけといてあげないといけない!」
と理解しました。
今後も自分がひっかかったところをどんどん記事にしていこうと思います!
最後まで読んでいただきありがとうございました。

参考文献

Swift実践入門

0
1
0

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
0
1