Avatar ·

Ошибка в Swift: свойство не инициализировано при вызове super.init

Привет! Я новичок в Swift и столкнулся с ошибкой, которую не могу понять. Пишу простое приложение для заметок. У меня есть базовый класс `Note` и подкласс `TaskNote`, который добавляет дату дедлайна. Вот код: ```swift class Note { var title: String var content: String init(title: String, content: String) { self.title = title self.content = content } } class TaskNote: Note { var deadline: Date init(title: String, content: String, deadline: Date) { self.deadline = deadline super.init(title: title, content: content) } } ``` При компиляции Xcode выдаёт ошибку: "Property 'self.deadline' not initialized at super.init call". Я думал, что сначала нужно присвоить значение новому свойству, а потом вызывать родительский инициализатор. Но компилятор говорит обратное. Почему так? Ведь в Objective-C всё работало иначе. Я перепробовал разные варианты: менял порядок строк, пытался сделать `deadline` опциональным (`Date?`), но тогда теряется смысл — дата дедлайна обязательна. Подскажите, как правильно инициализировать подкласс с собственными свойствами? И главное — объясните причину такого поведения Swift. Заранее спасибо! <<>>

Причина ошибки: двухфазная инициализация в Swift

Ошибка возникает из-за строгой системы инициализации в Swift, которая следует правилу двухфазной инициализации (two-phase initialization). Это ключевое отличие от Objective-C, где инициализация была более свободной. Swift гарантирует, что все свойства объекта будут инициализированы до того, как объект станет доступным для использования.

В вашем коде проблема в порядке вызовов: сначала вы присваиваете значение свойству deadline, а затем вызываете super.init. Но Swift требует, чтобы перед вызовом super.init были инициализированы только те свойства, которые не зависят от родительского класса. Однако есть нюанс: вы не можете вызывать методы или обращаться к self до завершения super.init. Присвоение значения свойству подкласса — это обращение к self, что запрещено до вызова родительского инициализатора.

Правильный порядок:

  1. Сначала вызывается super.init
  2. Затем инициализируются собственные свойства подкласса

Решение: измените порядок инициализации

Вот исправленный код:

class TaskNote: Note {
    var deadline: Date
    
    init(title: String, content: String, deadline: Date) {
        // Шаг 1: вызываем super.init
        super.init(title: title, content: content)
        // Шаг 2: инициализируем собственное свойство
        self.deadline = deadline
    }
}

Почему так? Разбор двухфазной инициализации

Процесс инициализации в Swift состоит из двух фаз:

  • Фаза 1: Инициализация всех свойств класса, начиная с подкласса и заканчивая базовым классом. На этом этапе вы не можете использовать self или вызывать методы — только присваивать значения свойствам.
  • Фаза 2: После того как все свойства инициализированы (через цепочку super.init), вы можете настраивать объект: вызывать методы, обращаться к self, менять свойства.

Когда вы пишете self.deadline = deadline до super.init, вы пытаетесь выполнить действие из фазы 2 (обращение к self), хотя ещё не завершена фаза 1. Swift блокирует это, чтобы избежать ситуаций, когда объект частично инициализирован, но к нему уже обращаются.

Альтернатива: инициализация через параметры по умолчанию

Если вы хотите сохранить порядок "сначала своё свойство, потом родительское", используйте значение по умолчанию для свойства подкласса:

class TaskNote: Note {
    var deadline: Date = Date() // Значение по умолчанию
    
    init(title: String, content: String, deadline: Date) {
        // Свойство уже инициализировано значением по умолчанию
        super.init(title: title, content: content)
        // Теперь можно перезаписать его
        self.deadline = deadline
    }
}

Это работает, потому что свойство deadline инициализируется прямо в объявлении (фаза 1 завершена до вызова super.init), а затем вы просто меняете его значение в фазе 2.

Дополнительный совет: используйте опциональные свойства с осторожностью

Иногда разработчики делают свойства опциональными, чтобы избежать ошибки:

class TaskNote: Note {
    var deadline: Date? // Опциональное свойство автоматически nil
    
    init(title: String, content: String, deadline: Date) {
        super.init(title: title, content: content)
        self.deadline = deadline
    }
}

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

Запомните правило: сначала super.init, потом всё остальное. Это основа безопасности типов в Swift.

<<>>

Использование convenience-инициализатора как альтернатива

Другой подход — вынести логику инициализации в convenience-инициализатор. Это позволяет сначала задать значения всех свойств подкласса, а затем делегировать основную работу designated-инициализатору родителя. Вот пример:

class TaskNote: Note {
    var deadline: Date
    
    convenience init(title: String, content: String, deadline: Date) {
        // Сначала задаём значение для deadline
        self.deadline = deadline
        // Вызываем designated-инициализатор родителя
        self.init(title: title, content: content)
    }
}

Но этот код не скомпилируется! В Swift convenience-инициализатор должен вызывать self.init (другой инициализатор того же класса), а не super.init. И перед вызовом self.init нельзя присваивать значения свойствам, так как это нарушает двухфазную инициализацию. Так что данный вариант не работает напрямую.

Однако можно сделать так: объявить свойство deadline как var с значением по умолчанию (например, Date()), а затем в convenience-инициализаторе просто перезаписать его после вызова self.init:

class TaskNote: Note {
    var deadline: Date = Date() // Значение по умолчанию
    
    convenience init(title: String, content: String, deadline: Date) {
        self.init(title: title, content: content) // Вызов designated-инициализатора
        self.deadline = deadline // Перезапись после инициализации
    }
}

Это работает, но требует значения по умолчанию. Если вам нужно гарантировать, что deadline всегда задаётся пользователем, лучше придерживаться первого решения из основного ответа. Convenience-инициализаторы удобны, когда нужно добавить дополнительные способы создания объекта, но они не решают проблему обязательных свойств.