Спрогнозируйте результат и объясните причину: нужно создать три независимых контейнера, но массив заполнен ...

Спрогнозируйте результат и объясните причину: нужно создать три независимых контейнера, но массив заполнен одним повторяемым значением.

final class Box {
    var value: Int
    init(_ value: Int) { self.value = value }
}

var boxes = Array(repeating: Box(0), count: 3)
boxes[0].value = 42

print(boxes.map { $0.value })
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Будет напечатано [42, 42, 42]. Инициализатор Array(repeating:count:) повторяет одно и то же значение, а Box — ссылочный тип, поэтому все элементы массива указывают на один объект.

Исторический контекст

В Swift коллекции вроде Array обладают семантикой значений, но это свойство самой коллекции не превращает автоматически её элементы в независимые копии. Если элементом является экземпляр класса, массив хранит ссылки на объекты.

Инициализатор Array(repeating:count:) предназначен для заполнения массива повторяющимся значением. Он не обязан вызывать конструктор или выполнять копирование элемента для каждой позиции.

Постановка проблемы

Разработчик может ожидать, что три элемента массива означают три независимых объекта. Для ссылочного типа это неверно: изменение объекта через любую ссылку видно через остальные элементы.

Такая ошибка особенно опасна при создании массивов состояний, моделей интерфейса или буферов. Изменение одного элемента может незаметно изменить все логически «разные» элементы.

Подробное решение

Выражение Box(0) создаёт один объект. Затем Array(repeating:count:) записывает ссылку на него во все три позиции. Операция boxes[0].value = 42 изменяет сам объект, поэтому все ссылки наблюдают значение 42.

final class Box { var value: Int init(_ value: Int) { self.value = value } } var boxes = (0..<3).map { _ in Box(0) } boxes[0].value = 42 print(boxes.map { $0.value }) // [42, 0, 0]

Здесь map выполняет замыкание для каждого элемента диапазона и создаёт новый экземпляр Box на каждой итерации. Поэтому результат содержит три разные ссылки.

Если элемент — структура, повторение значения обычно не создаёт такого эффекта: изменение одного элемента массива не изменяет другие. Однако структура может содержать ссылочные поля, и тогда общий объект всё равно может быть разделён между копиями.

Ключевой компромисс такой: Array(repeating:) краток и эффективен, когда элементы должны быть одинаковым значением или неизменяемым объектом; map или цикл нужен, когда для каждой позиции требуется отдельный экземпляр.

Ситуация из практики

Допустим, экран создаёт три независимых состояния ячеек. Вариант с Array(repeating: CellState(), count: 3) создаёт одну модель и три ссылки на неё. Плюс этого варианта — простота, но результат некорректен: изменение состояния одной ячейки меняет остальные.

Можно использовать map { _ in CellState() }. Это явно создаёт объект на каждой итерации, но требует конструктора без побочных эффектов и немного более многословно. Если состояние естественно представить структурой, предпочтительнее хранить struct CellState: копирование значения уменьшает риск случайного совместного состояния.

Практический выбор — создавать элементы через map или цикл, если объекты должны быть независимыми. После этого изменение одной ячейки затрагивает только её состояние.

Что кандидаты часто упускают

  1. Разве Array не копируется при изменении из-за copy-on-write?

    Copy-on-write разделяет буфер самого массива, но не клонирует объекты, на которые указывают его элементы. Даже после копирования массива ссылки внутри него могут указывать на те же экземпляры Box.

  2. Будет ли проблема, если повторяемый объект неизменяемый?

    Если объект действительно не имеет изменяемого состояния, совместное использование обычно безопасно и может быть полезным. Однако наличие let у ссылки запрещает заменить ссылку, но не делает объект класса неизменяемым: его свойства могут оставаться var.

  3. Достаточно ли заменить класс на структуру?

    Не всегда. Структура имеет семантику значения только на верхнем уровне. Если она содержит, например, свойство var storage: NSMutableArray, копии структуры могут совместно использовать один изменяемый ссылочный объект. Нужно анализировать весь граф хранимых данных, а не только внешний тип.