Что произойдёт с willSet и didSet хранимого свойства при присваивании ему начального значения внутри инициализатора?
При присваивании начального значения хранимому свойству внутри инициализатора willSet и didSet не вызываются. Наблюдатели начинают работать после завершения инициализации, когда свойство изменяется обычным присваиванием.
Это правило относится к этапу создания объекта: Swift записывает начальные значения напрямую, не рассматривая их как последующие изменения уже существующего экземпляра.
Наблюдатели свойств нужны, чтобы привязать побочные действия к изменению состояния: обновление интерфейса, журналирование, пересчёт производных данных или отправку уведомлений. Они позволяют не дублировать такой код в каждом месте, где свойство изменяется.
Во время инициализации объект ещё не считается полностью готовым. Поэтому запускать наблюдатели на этом этапе было бы опасно: другие свойства могут быть не инициализированы, а методы или внешние компоненты могут увидеть частично созданный объект.
Если разработчик ожидает вызов didSet при начальном присваивании, он может ошибочно рассчитывать на первичную настройку интерфейса, регистрацию события или вычисление зависимого состояния.
В результате начальное состояние будет установлено, но побочный код наблюдателя не выполнится. При этом последующее присваивание тому же свойству после завершения инициализатора уже вызовет наблюдатели.
Swift разделяет два процесса:
Начальные значения хранимых свойств, заданные непосредственно в инициализаторе или через значения по умолчанию, не считаются наблюдаемыми изменениями. Поэтому willSet и didSet пропускаются.
После завершения инициализатора обычное присваивание вызывает наблюдатели в таком порядке: сначала willSet, затем фактическая запись нового значения, затем didSet. В willSet доступно новое значение, а в didSet — старое значение через специальное имя oldValue.
При создании экземпляра напечатается только init done. При последующем присваивании будут вызваны сначала willSet, затем didSet.
Наблюдатели применяются к хранимым свойствам. Для вычисляемого свойства вместо них используются собственные get и set. Если необходима общая логика и для начального состояния, и для последующих изменений, её следует вызвать явно после инициализации либо вынести в отдельный метод, отвечающий за синхронизацию состояния.
Есть модель экрана с хранимым свойством status. В didSet разработчик обновляет индикатор загрузки. При создании модели статус получает значение ready, но индикатор не обновляется, потому что это начальная инициализация, а не последующая мутация.
Возможны три решения. Можно вручную вызвать обновление в конце инициализатора: это просто, но создаёт зависимость модели от конкретного побочного действия. Можно заменить свойство вычисляемым: это подходит, если обновление полностью выражается через get и set, но усложняет хранение состояния. Можно вынести синхронизацию в отдельный метод applyCurrentState и вызывать его после создания модели и в местах последующих изменений.
На практике обычно выбирают явную синхронизацию состояния на границе модели и интерфейса. Наблюдатель оставляют для изменений после инициализации, а первичную настройку выполняют отдельным вызовом. Так поведение не зависит от невызываемых на этапе инициализации наблюдателей, а ответственность за начальное отображение остаётся очевидной.
Да. Само присваивание после инициализации считается изменением свойства, поэтому willSet и didSet обычно вызываются даже при равных старом и новом значениях. Если повторная обработка нежелательна, сравнение значений нужно выполнить явно, например в начале didSet, используя oldValue.
Для операции inout Swift временно предоставляет функции доступ к переменной, а затем записывает результат обратно. Наблюдатели свойства не должны восприниматься как вызываемые при каждом внутреннем изменении локального значения; для свойства они срабатывают при возврате результата и записи обратно. Поэтому код не следует проектировать с расчётом на несколько вызовов didSet внутри одной операции inout.
Да, присваивание этому же свойству из собственного наблюдателя не приводит к бесконечному повторному вызову того же наблюдателя. Это позволяет, например, нормализовать значение непосредственно в didSet, однако такой подход следует применять осторожно: сложные взаимные изменения нескольких свойств могут создать трудно отслеживаемые побочные эффекты. Для нетривиальной валидации часто яснее использовать отдельный метод или вычисляемое свойство с контролируемым set.