レイジーシーケンスは、コレクションへの特別なラッパーで、計算(フィルター、マップなど)を要素にアクセスするまで遅延させることができます。これは、レイジーシーケンスに対する操作は、結果が参照されるときだけ計算されることを意味します(例えば、.forEach、.first、配列への変換などの呼び出し時)。
使用するタイミング:
例:
let numbers = Array(1...1_000_000) let lazyNumbers = numbers.lazy.map { $0 * 2 }.filter { $0 % 3 == 0 } let first = lazyNumbers.first // 適切な最初の要素のための操作チェーンがこの時初めて計算されます!
落とし穴:
配列での
.map { ... }と.lazy.map { ... }の呼び出しの違いは何ですか?
回答:
.map { ... }はクロージャを各要素に適用し、すぐに新しい配列を返します。つまり、すべての要素が処理され、メモリに保存されます。.lazy.map { ... }は配列ではなくレイジーシーケンス(ラッパー)を返し、要素の処理を即座には行わず、アクセス時にのみ行います。例:
let a = Array(1...10) let eagers = a.map { $0 * 2 } // 10要素の配列 let laziers = a.lazy.map { $0 * 2 } // 結果を即座に持たないレイジーシーケンス
物語
大規模なプロジェクトで、開発者は非常に大きなデータ配列に対してmap、filter、reduceをいくつか連鎖させ、.lazyを使用しませんでした。これにより、各中間ステップで大きな配列が一時的に割り当てられ、メモリ消費がほぼ2倍に増加し、RAMの少ないデバイスでクラッシュが発生しました。
物語
コードブロック内で副作用のあるレイジーシーケンスが内部クロージャで使用されていました(例えば、map/filter内でのイベント送信や印刷)。開発者はこの操作が即座に実行されると期待しましたが、実際には一度もレイジーシーケンスの要素にアクセスされず、イベントが全く発生しませんでした。その結果、ログやメトリクスが不正確になりました。
物語
大きなデータベースからのデータを集約する場合に、複数回の通過(例えば、firstを2回検索し、その後countを計算する)でレイジーシーケンスを使用しました。レイジーシーケンスの各通過は操作の完全な再計算を引き起こし、二重の遅延とシステムへの不必要な負荷を引き起こしました。通常の配列に置き換えたところ、問題は解決しました。