ПрограммированиеiOS разработчик

Что такое lazy sequence в Swift? Как он работает, когда стоит его использовать, и какие есть подводные камни?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Lazy sequence — это специальная обёртка над коллекцией, которая позволяет отложить выполнение вычислений (filter, map и т.д.) до момента непосредственного доступа к элементам. Это значит, что операции над lazy sequence будут вычислены только тогда, когда к их результату обратятся (например, при вызове .forEach, .first, преобразовании в массив и др.).

Когда использовать:

  • При работе с большими последовательностями, если нет необходимости обрабатывать все элементы сразу.
  • Когда хотим избежать промежуточных аллокаций при последовательных трансформациях (map -> filter -> ...).

Пример:

let numbers = Array(1...1_000_000) let lazyNumbers = numbers.lazy.map { $0 * 2 }.filter { $0 % 3 == 0 } let first = lazyNumbers.first // Только сейчас вычислится цепочка операций для первого подходящего элемента!

Подводные камни:

  • Вычисления происходят каждый раз заново при каждом проходе по .lazy sequence.
  • Зависят от порядка вызова: если нужно несколько раз пройти по данным с разными условиями — данные будут перерасчитаны.
  • Отложенные действия могут быть источником неожиданных side effects, если во вложенных closure есть побочные эффекты.

Вопрос с подвохом.

Чем отличается вызов .map { ... } от .lazy.map { ... } на массиве?

Ответ:

  • .map { ... } применяет closure к каждому элементу и возвращает новый массив сразу, то есть все элементы будут обработаны и сохранены в памяти.
  • .lazy.map { ... } возвращает не массив, а lazy-sequence (обёртку), которая не выполняет обработку элементов сразу, а только при доступе к ним.

Пример:

let a = Array(1...10) let eagers = a.map { $0 * 2 } // Массив с 10 элементами let laziers = a.lazy.map { $0 * 2 } // LazySequence, не содержащий сразу результатов

Примеры реальных ошибок из-за незнания тонкостей темы.


История

В большом проекте разработчик применил цепочку из нескольких вызовов map, filter, reduce к огромному массиву данных без .lazy. Это привело к временному аллоцированию больших массивов на каждый промежуточный шаг, увеличило потребление памяти почти в два раза и спровоцировало краши на некоторых устройствах с низким объёмом ОЗУ.


История

В блоке кода была использована lazy sequence с побочным эффектом во внутреннем замыкании (например, send event или print внутри map/filter). Разработчик ожидал, что эта операция выполнится немедленно, однако событие не произошло вообще — потому что к элементам lazy sequence ни разу не обратились, и код с событием не был вызван вовсе. В результате логи и метрики оказались некорректными.


История

В случае сбора статистики по данным из большой базы использовали lazy sequence в сочетании с несколькими проходами (например, дважды искали first, а потом считали count). Каждый проход по lazy sequence инициировал полный пересчёт операций — что привело к двукратному замедлению и ненужной нагрузке на систему. После замены на обычный массив проблема исчезла.