Lazy sequence는 컬렉션에 대한 특별한 래퍼로, 요소에 직접 접근할 때까지 계산 수행을 지연시킬 수 있게 해줍니다 (filter, map 등). 즉, lazy sequence에 대한 연산은 결과에 접근할 때만 계산됩니다 (예: .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 { ... }는 배열이 아니라 lazy-sequence(래퍼)를 반환하며, 이는 요소에 접근할 때까지 처리를 즉시 수행하지 않습니다.예:
let a = Array(1...10) let eagers = a.map { $0 * 2 } // 10개의 요소를 가진 배열 let laziers = a.lazy.map { $0 * 2 } // 결과가 즉시 포함되지 않는 LazySequence
이야기
큰 프로젝트에서 개발자는 .lazy 없이 거대한 데이터 배열에 map, filter, reduce의 여러 호출 체인을 적용했습니다. 이로 인해 각 중간 단계에서 큰 배열이 일시적으로 할당되어 메모리 소비가 거의 두 배로 증가했으며, 일부 낮은 RAM 장치에서 크래시를 유발했습니다.
이야기
코드 블록에서 내부 클로저에 부작용이 있는 lazy sequence가 사용되었습니다 (예: map/filter 내에서 이벤트 전송 또는 출력). 개발자는 이 작업이 즉시 수행될 것이라고 기대했지만, lazy sequence의 요소에 한 번도 접근하지 않아 이벤트가 전혀 발생하지 않았습니다. 결과적으로 로그와 메트릭이 부정확했습니다.
이야기
거대한 데이터베이스에서 데이터를 기반으로 통계를 수집할 때 여러 번의 반복과 함께 lazy sequence를 사용했습니다 (예: 두 번 first를 찾고, 그 다음 count를 계산했습니다). lazy sequence를 반복할 때마다 연산의 전체 재계산이 이루어져 두 배의 지연과 불필요한 시스템 부하를 초래했습니다. 일반 배열로 바꾸자 문제가 사라졌습니다.