ProgrammingiOS開発者

Swiftにおけるescapingとnon-escapingクロージャとは何ですか?クロージャの動作を正しく構成する方法、使用に際しての注意点、非同期コードでの使用時に何が問題になる可能性があるかについて説明してください。

Hintsage AIアシスタントで面接を突破

回答。

Escapingクロージャとは、呼び出し元の関数のスコープを"離れる"クロージャ(例えば、非同期実行のために保存されるもの)です。

Swiftでは、デフォルトで関数はnon-escapingクロージャを受け取ります:クロージャは関数の呼び出し内で実行されます。

escapingを明示的に指定するには、@escapingキーワードを使用します:

func asyncTask(completion: @escaping () -> Void) { DispatchQueue.main.async { completion() } }

主な違い:

  • Escapingクロージャは、後で保存されて実行されることができます。
  • escapingクロージャ内でselfをキャプチャするには、メモリリーク(retain cycle)を防ぐために、明示的に[weak self]または[unowned self]を指定する必要があります。

ひねりのある質問。

DispatchQueue.asyncを呼び出す関数のパラメータとして渡されるクロージャには、常に@escapingを書く必要がありますか?

— はい。DispatchQueue.asyncはクロージャを実行時まで保存するため、クロージャはescapingになります。

例:

func foo(action: () -> Void) { DispatchQueue.main.async { action() // コンパイルされません:クロージャはescapingである必要があります。 } } // 必要なのは: func bar(action: @escaping () -> Void) { ... }

このトピックの微妙な点を知らないことによる実際のエラーの例。


事例

コントローラがescapingクロージャ内でselfへの強い参照を作成した(例えば、ネットワークリクエスト)。コントローラは画面から離れても解放されなかった — 強いretain cycle。解決策:[weak self]または[unowned self]を使用する。


事例

関数がDispatchQueue.asyncにクロージャを渡したが、それをescapingとしてマークしなかった。プロジェクトはコンパイルされず、エラーは関数の入れ子構造のために見つけにくかった。


事例

クロージャ内で、クロージャが呼び出される時点で既にデイニシャライズされたオブジェクトにアクセスした([unowned self]を使用)。結果 — ランタイムクラッシュ。解決策:[weak self]を使用し、nilチェックを行う。