ProgrammingiOS開発者

Swiftにおけるクロージャの動作を説明してください:関数との違い、変数のキャプチャに関する特徴、誤った使用による潜在的な問題。

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

回答

Swiftのクロージャは、周囲のコンテキストから変数や定数への参照をキャプチャして保存できる自己完結型のコードブロックです。コールバック、非同期処理を整理し、コードの実行を変数の値として保持することを可能にします。

関数との違い

  • クロージャは外部スコープの変数をキャプチャできます。
  • よりコンパクトな構文を持っています。
  • 値として渡すことができます。

クロージャの例:

var counter = 0 let incrementer: () -> Void = { counter += 1 } incrementer() print(counter) // 1

キャプチャの特徴

  • デフォルトでは、変数はstrong(強い参照)でキャプチャされます。
  • キャプチャリスト([weak self][unowned self])を使って明示的にキャプチャのルールを定義できます。

問題

  • 主な問題は、クロージャ内でselfをキャプチャする際の可能なretain cycleです。
  • マルチスレッドコードでは、古い変数の値をキャプチャする可能性があります。

トリックのある質問

実際のキャッチリスト内での[weak self]または[unowned self]の使用とstrongキャプチャの違いは何ですか?それにはどういうリスクが伴いますか?

回答: [weak self]または[unowned self]を指定しない場合、クロージャはデフォルトでselfを強い参照でキャプチャしますが、これはクロージャとselfが互いに参照しあう場合にretain cycleを引き起こすことになります。[weak self]はselfを弱い参照(optional)でキャプチャし、[unowned self]は制御されない参照(non-optional)でキャプチャします。キャプチャリストの不適切な選択や不在は、メモリリークやクラッシュを引き起こします。

class Test { var closure: (() -> Void)? func setup() { closure = { [weak self] in self?.doWork() } } func doWork() { ... } }

このテーマの微妙な知識を知らないことから発生する実際のエラーの例


物語

あるプログラマーは、非同期データロードを開始するUIViewController内のクロージャでキャプチャリストを使用しませんでした。その結果、コントローラーとそのビューは解放されず、クローズ後にメモリリークが発生しました。インフラ分析では、メモリ内で「ハング」しているコントローラーが何百も見つかりました。


物語

クロージャは[unowned self]を使用してselfをキャプチャしましたが、その際selfのライフサイクルがクロージャよりも早く終了していました。解放されたオブジェクトへのアクセスでクラッシュが発生し、クロージャ内でselfを使用する際にアプリがクラッシュしました。


物語

プロジェクト内で、ネストされたクロージャが二つの変数をキャプチャしました:一つはstrongで、もう一つはweakで。弱い参照があまりにも早くnullになり、クロージャに必要なデータが失われました。このバグはマルチスレッド環境でのストレステスト中のみに現れました。