問題の歴史:
エスケープ分析は、コンパイラの最適化とメモリ管理の用語です。Swiftでは、クロージャとARCの積極的な利用により重要です。エスケープクロージャとノンエスケープクロージャの概念に関連しており、これによりクロージャが関数のスコープを超えて出て行くことができるかどうかが決まります。
問題:
クロージャのタイプを誤って定義すると、メモリの所有権の誤り、リーク、変数の予期しないキャプチャ、およびパフォーマンスの低下を引き起こす可能性があります。クロージャが「逃げる」(escapes) のかどうか、またはそうでないのかを明確に理解し、それを正しくマークする必要があります。
解決策:
Swiftでは、エスケープクロージャを明示的に@escaping属性でマークすることが要求されます。ノンエスケープクロージャは関数の内部でのみキャプチャされ、メモリ管理が自動的により効率的で、キャプチャされた変数をより安全に利用できます。
違いの例:
// ノンエスケープクロージャ(高速で、呼び出し後は保持されない) func performSync(block: () -> Void) { block() } // エスケープクロージャ(保存され、後で実行できる) var storedCompletion: (() -> Void)? func performAsync(block: @escaping () -> Void) { storedCompletion = block }
主な特徴:
ノンエスケープとして定義されたクロージャを、関数のソースコードを変更せずにエスケープに変更できますか?
いいえ。@escaping属性は関数のシグネチャ内で明示的に指定する必要があります。関数内のクロージャがその範囲を超えて渡される場合、エスケープクロージャのみが必要であり、そうでなければコンパイラはエラーを報告します。
エスケープクロージャ内でweak/unownedキャプチャなしでselfを渡すことは安全ですか?
いいえ。エスケープクロージャは、強い参照でselfをキャプチャすると、リテインサイクルを引き起こす可能性があります。キャプチャを明示的に管理する必要があります:
someAsync { [weak self] in self?.doSomething() }
エスケープクロージャは常にグローバルですか?(プログラムの終了までメモリに保持されますか?)
いいえ。そのライフサイクルはストレージの範囲に依存します。クロージャが一時的に保存されるか、プロパティがnilにされると、所有者が失われるとすぐに解放されます。グローバルになるのは、グローバルオブジェクトへの参照を持ち、またはグローバル変数に保持されるクロージャのみです。
クラスのメソッド内で@escapingなしでクロージャを保存する(コンパイラエラー)、後で修正するがリテインサイクル防止を忘れ、アプリがメモリリークを起こす。
利点:
欠点:
開発者は常にエスケープクロージャが必要な場所を確認し、selfをweak/unownedとしてキャプチャし、リークを排除し、安全にメモリを管理します。
利点:
欠点: