Можно ли в Swift гарантировать повторный обход любого значения типа Sequence?
Нет. Протокол Sequence не гарантирует, что последовательность можно безопасно обходить повторно: конкретная реализация может быть одноразовой, зависеть от внешнего состояния или генерировать элементы заново при каждом обходе. Гарантии многократного обхода следует ожидать от Collection, а не от произвольного Sequence.
Sequence отделяет способ получения элементов от их хранения. Это позволяет одинаково обрабатывать массивы, ленивые преобразования, генераторы и потоки, не требуя от всех источников индексов или заранее известного размера.
Такое обобщение требует компромисса: последовательность может быть конечной или бесконечной, дешёвой или дорогой для повторного создания, а иногда — вообще связанной с одноразовым ресурсом. Поэтому протокол задаёт минимальный контракт обхода, но не обещает многопроходность.
Цикл for-in каждый раз запрашивает итератор через makeIterator(), однако это не означает, что новый итератор будет независимым. Реализация может разделять состояние между итераторами, например позицию чтения файла, сетевой поток или генератор случайных значений.
Если ошибочно считать любой Sequence повторно обходимым, второй проход может вернуть пустой результат, другие значения, вызвать побочные эффекты или повторно выполнить дорогую операцию. Особенно опасно передавать такую последовательность в несколько функций, каждая из которых ожидает независимый обход.
Механизм Sequence основан на связке makeIterator() и IteratorProtocol. Итератор хранит текущее состояние обхода, а вызовы next() продвигают это состояние до тех пор, пока не будет возвращено nil.
Здесь оба обхода получают итератор, обращающийся к одному общему состоянию. Первый проход исчерпывает его, поэтому второй не получает элементов. Это допустимое поведение для Sequence.
Collection предоставляет более сильную модель: коллекции являются многопроходными, а их обход строится вокруг индексов. Однако многопроходность не означает одинаковую стоимость каждого прохода или отсутствие побочных эффектов в пользовательских обёртках; также нужно учитывать изменения коллекции и потокобезопасность.
Если нужны стабильные повторные проходы, применяют один из подходов:
Array, когда объём данных приемлем;Материализация даёт предсказуемость и быстрый повторный доступ, но требует памяти и ожидания завершения источника. Сохранение ленивого Sequence экономит память, однако повторный обход может быть невозможен или снова запустить вычисления.
Сервис получает страницы данных из сети и представляет их как ленивую последовательность. Один компонент вычисляет количество элементов, а другой затем пытается отправить сами элементы в хранилище. Если оба компонента используют один одноразовый источник, первый обход может полностью потребить поток, и хранилище получит пустой результат.
Рассматривались два варианта. Материализация всех страниц в массив упрощает повторное использование, но увеличивает пиковое потребление памяти. Повторный запуск сетевого запроса сохраняет память, но удваивает задержку, нагрузку на сервер и риск получить уже изменившиеся данные.
Выбранным решением стала однократная обработка: в одном проходе одновременно считали количество и записывали элементы, а для небольшого результата сохранили ограниченный буфер. Это устранило предположение о повторном обходе, сохранило потоковую обработку и не потребовало второго запроса.
Означает ли новый вызов makeIterator() независимый обход?
Нет. Это лишь запрос итератора; реализация сама определяет, какое состояние он использует. Итераторы могут быть независимыми, разделять состояние или обращаться к внешнему ресурсу, поэтому сам факт создания нового итератора не гарантирует повторяемость.
Гарантирует ли тип Collection сохранение одного и того же результата при повторном обходе?
Он гарантирует многопроходную модель обхода, но не универсальную неизменность данных во времени. Если коллекция изменяется между проходами, результаты могут отличаться; кроме того, пользовательская коллекция может получать данные через внешний изменяемый источник. Для стабильного снимка нужно явно зафиксировать данные, например материализовать их в отдельное значение.
Почему преобразование Sequence в массив меняет семантику повторного использования?
Преобразование полностью потребляет исходную последовательность и сохраняет полученные элементы в собственной коллекции. Последующие обходы массива уже не зависят от состояния исходного итератора, но цена этого — память, время полной материализации и невозможность бесконечного источника. Для бесконечных или очень больших последовательностей такой подход может быть неприемлем.