Можно ли заставить didSet вызываться при инициализации в Swift?
Почему didSet не вызывается при инициализации?
Это поведение заложено в Swift намеренно. Во время инициализации свойствам только присваиваются начальные значения, и наблюдатели (willSet/didSet) игнорируются. Swift считает, что в этот момент объект ещё не полностью готов к использованию, поэтому вызов наблюдателей может привести к нежелательным побочным эффектам (например, обращение к другим, ещё не инициализированным свойствам).
Решение №1: Вызов didSet вручную (простой способ)
Самый прямолинейный способ — явно вызвать код из didSet после присваивания в инициализаторе:
class Temperature {
var celsius: Double = 0 {
didSet {
print("Температура изменилась на \(celsius)°C")
validateTemperature()
}
}
init(celsius: Double) {
self.celsius = celsius
// Явно вызываем логику didSet
print("Температура изменилась на \(celsius)°C")
validateTemperature()
}
private func validateTemperature() {
// Валидация или обновление UI
}
}
Минусы: дублирование кода. Если логика в didSet сложная, легко забыть обновить её в инициализаторе.
Решение №2: Использование defer (элегантнее)
Можно поместить вызов наблюдателя в блок defer, который выполнится после завершения инициализации:
class Temperature {
var celsius: Double = 0 {
didSet {
print("Температура изменилась на \(celsius)°C")
}
}
init(celsius: Double) {
defer { self.celsius = celsius }
// Другая инициализация, если нужно
}
}
Хитрость в том, что defer выполняется после того, как все свойства уже инициализированы. Присваивание внутри defer запускает didSet, так как объект уже считается полностью инициализированным.
Решение №3: Фабричный метод (рекомендуемый)
Лучшая практика — вынести создание объекта в статический фабричный метод, где didSet будет работать нормально:
class Temperature {
var celsius: Double = 0 {
didSet {
print("Температура изменилась на \(celsius)°C")
}
}
private init() {} // Приватный инициализатор
static func create(celsius: Double) -> Temperature {
let temp = Temperature()
temp.celsius = celsius // Здесь didSet уже вызывается!
return temp
}
}
// Использование
let temp = Temperature.create(celsius: 25) // "Температура изменилась на 25.0°C"
Плюсы: чистый код, нет дублирования, инициализация через публичный интерфейс.
Решение №4: computed property с backing storage (для сложных случаев)
Если у вас много свойств и сложная логика, используйте отдельное хранилище:
class Temperature {
private var _celsius: Double = 0
var celsius: Double {
get { _celsius }
set {
_celsius = newValue
print("Температура изменилась на \(_celsius)°C")
}
}
init(celsius: Double) {
self.celsius = celsius // Вызовет set и print
}
}
Здесь мы полностью контролируем процесс присваивания, и наблюдатель срабатывает всегда.
Итог
- Для простых случаев — используйте фабричный метод или
defer. - Для сложных проектов — computed property с backing storage даёт полный контроль.
- Избегайте дублирования кода из didSet в инициализаторе — это чревато ошибками.
Выбор зависит от контекста, но помните: Swift не просто так отключает наблюдатели при инициализации — это защита от нестабильного состояния объекта.
<<Альтернативный подход: Использование willSet и проверка инициализации
Можно комбинировать willSet и didSet, чтобы элегантно обрабатывать начальное присваивание. Добавьте флаг, который показывает, завершена ли инициализация:
class Temperature {
private var isInitialized = false
var celsius: Double = 0 {
willSet {
if isInitialized {
// Действия до изменения, только после инициализации
}
}
didSet {
if isInitialized {
print("Температура изменилась на \(celsius)°C")
} else {
// Первое присваивание — выполняем нужные действия
print("Начальная температура установлена: \(celsius)°C")
isInitialized = true
}
}
}
init(celsius: Double) {
self.celsius = celsius
// isInitialized ещё false, didSet выполнит только начальную логику
}
}
Плюсы: не нужно дублировать код, вся логика остаётся внутри наблюдателя. Минусы: добавляется служебное свойство, что может быть избыточно для простых случаев.
Этот подход удобен, когда нужно различать первое присваивание (в инициализаторе) и последующие изменения. Например, для аналитики или логирования первого запуска.