Ситуация: класс соответствует протоколу с требуемым инициализатором. Зачем реализацию обычно помечают как required?
Инициализатор, которым класс удовлетворяет требованию протокола, обычно объявляют с модификатором required, чтобы каждый подкласс этого класса сохранял возможность удовлетворять тому же требованию. Это заставляет компилятор потребовать такой инициализатор у подкласса или проверить, что он корректно унаследован.
Для final-класса required не нужен: у него нет подклассов, которым пришлось бы наследовать это обязательство.
Протоколы в Swift описывают контракт типа, включая способы его создания. Для структур и перечислений соответствующий инициализатор реализуется непосредственно, но у классов появляется дополнительная проблема наследования: подкласс может иметь собственные хранимые свойства и собственную цепочку инициализации.
Модификатор required связывает соответствие протоколу с правилами наследования классов. Он предотвращает ситуацию, когда базовый класс формально поддерживает протокол, а его подкласс уже нельзя создать через требуемый протоколом инициализатор.
Предположим, протокол требует создать значение из строки, а базовый класс реализует такой инициализатор. Если реализацию не сделать обязательной для подклассов, подкласс может добавить обязательные свойства и не предоставить совместимый путь инициализации.
Это важно не только при прямом вызове инициализатора. Код, работающий с метатипом или generic-параметром, ограниченным этим протоколом, должен иметь гарантированный доступ к требуемому инициализатору независимо от конкретного класса.
Если не final-класс соответствует протоколу с инициализатором, реализация требования должна быть помечена required. В противном случае Swift выдаёт ошибку компиляции, потому что обязательство не будет корректно распространено на наследников.
В Report инициализатор init(text:) тоже должен быть required, потому что он переопределяет обязательное требование базового класса. Он может быть унаследован автоматически, если правила наследования инициализаторов это позволяют; тогда писать новую реализацию не требуется.
Для класса, объявленного как final, обязательность для будущих подклассов бессмысленна:
Инициализатор протокола не обязан быть required в структуре или перечислении, поскольку у них нет классовых наследников. Также required не означает, что инициализатор можно вызвать через любой existential протокола: доступность вызова зависит от самого объявления требования и от способа использования типа.
В SDK есть протокол фабричных объектов, а базовый класс хранит общую конфигурацию. Один вариант — объявить требуемый инициализатор только в базовом классе без required. Это не даёт корректного контракта для наследников: подкласс может перестать поддерживать унифицированное создание.
Второй вариант — заставить каждый подкласс заново объявлять соответствие протоколу. Это избыточно и не решает проблему наследования самого инициализатора.
Выбранное решение — реализовать требование в базовом классе и пометить инициализатор required. Тогда компилятор контролирует все неабстрактные подклассы, а подкласс либо наследует корректную реализацию, либо явно предоставляет собственную. Это сохраняет единый контракт фабрики и обнаруживает ошибку на этапе компиляции.
1. Нужно ли помечать required каждый инициализатор класса, соответствующий протоколу?
Нет. required нужен именно для инициализатора, который удовлетворяет требованию протокола, и только если класс не является final. Обычные инициализаторы, не связанные с контрактом протокола, обязательными не становятся.
2. Обязан ли каждый подкласс вручную реализовывать required-инициализатор?
Нет. Подкласс может унаследовать его по обычным правилам наследования инициализаторов Swift. Если добавленные свойства инициализируются значениями по умолчанию и остальные условия наследования выполнены, отдельная реализация не понадобится. Если наследование невозможно, подкласс обязан предоставить собственную реализацию с тем же требованием required.
3. Можно ли заменить требуемый инициализатор на более специализированный вариант?
Нет. Инициализатор должен удовлетворять точной сигнатуре требования протокола. Наличие другого инициализатора с дополнительными параметрами не заменяет требуемый, потому что generic-код или фабрика, знающие только протокол, не могут использовать дополнительные параметры.