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

При повторной записи того же значения в наблюдаемое свойство определите порядок и содержание вывода: пример...

При повторной записи того же значения в наблюдаемое свойство определите порядок и содержание вывода:

struct Temperature {
    var celsius: Int {
        willSet { print("willSet", newValue) }
        didSet { print("didSet", oldValue) }
    }
}

var temperature = Temperature(celsius: 20)
temperature.celsius = 20

Объясните, вызываются ли наблюдатели, если значение фактически не изменилось.

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

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

При присваивании temperature.celsius = 20 оба наблюдателя выполняются, даже если новое значение равно старому. Вывод будет таким:

willSet 20 didSet 20

willSet получает новое значение до записи, а didSet — старое значение после записи. Наблюдатели не выполняются только потому, что значения различаются: сам факт присваивания уже установленному свойству достаточен.

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

Наблюдатели свойств нужны для добавления реакции на изменение хранимого свойства без написания отдельного вычисляемого свойства с ручным getter и setter. Такой подход позволяет централизовать побочные действия: обновление интерфейса, журналирование, отправку уведомлений или поддержание производного состояния.

При этом наблюдатели не являются механизмом фильтрации изменений. Они уведомляют о присваивании, но не предотвращают его и не пропускают обработчик только при обнаружении отличия.

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

Разработчик может ошибочно ожидать, что didSet вызовется только при изменении значения. Это приводит к лишним сетевым запросам, повторной перерисовке интерфейса или ненужному пересчёту данных, если код присваивает свойству то же значение.

Важно также отличать присваивание от инициализации. Наблюдатели не вызываются при первоначальной инициализации хранимого свойства, потому что объект в этот момент ещё формируется и его состояние не считается обычным изменением.

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

При записи Swift выполняет операции в следующем порядке:

  1. вычисляет новое значение;
  2. вызывает willSet, передавая это новое значение через newValue;
  3. сохраняет значение в свойстве;
  4. вызывает didSet, передавая прежнее значение через oldValue.

В примере старое и новое значения равны 20, поэтому оба вызова получают число 20. Swift не выполняет автоматическое сравнение значений перед вызовом наблюдателей: для произвольного типа такое сравнение вообще может быть недоступно, поскольку тип не обязан поддерживать Equatable.

Если требуется реагировать только на реальное изменение, проверку нужно написать явно:

struct Counter { var value: Int { didSet { guard value != oldValue else { return } print("Изменилось") } } }

У такого решения есть ограничение: сравнение возможно только для типов, для которых определена подходящая операция сравнения. Для сложных значений также нужно заранее определить, что считать существенным изменением.

Наблюдатели применяются к хранимым свойствам экземпляра и типа, а также могут использоваться поверх унаследованного свойства. Они не превращают свойство в вычисляемое и не позволяют отменить присваивание. Если нужно валидировать или преобразовывать входное значение до сохранения, обычно нужен собственный setter, а не willSet или didSet.

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

Модель экрана хранит выбранный идентификатор и в didSet запускает загрузку данных. Пользователь повторно выбирает тот же элемент, и загрузка выполняется второй раз. Причина — didSet сообщает о присваивании, а не гарантированно об изменении значения.

Вариант с безусловным действием в didSet прост, но создаёт лишнюю работу. Вариант с проверкой oldValue устраняет повторные операции и обычно является достаточным решением. Вычисляемое свойство с ручным setter даёт больше контроля, включая возможность нормализовать или отклонить значение, но требует самостоятельно реализовать хранение и может быть избыточным для простого сравнения.

Практичный выбор — оставить хранимое свойство с didSet и добавить ранний выход при равенстве старого и нового значения. В результате повторное присваивание остаётся допустимым, но дорогостоящая побочная операция выполняется только при значимом изменении.

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

  1. Вызываются ли willSet и didSet, если новое значение равно старому?

Да. Для наблюдаемого хранимого свойства Swift не использует неявную проверку равенства перед вызовом наблюдателей. Если бизнес-логике нужно отфильтровать такие случаи, сравнение с oldValue добавляется явно.

  1. Вызываются ли наблюдатели при присваивании начального значения внутри инициализатора?

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

  1. Можно ли в willSet изменить записываемое значение или отменить присваивание?

Нет. willSet предназначен для реакции перед записью и получает новое значение только для чтения в рамках этого наблюдателя. Он не является валидатором и не может заменить или отменить значение; для преобразования, проверки или отклонения записи нужен собственный setter.