В какой момент Swift разрешает присваивание хранимому свойству класса, объявленному как let, и что меняется после завершения designated-инициализатора?
Хранимому свойству класса с модификатором let можно присвоить значение во время инициализации экземпляра, если оно ещё не было инициализировано. После завершения designated-инициализатора свойство считается установленным навсегда: его нельзя переназначить.
Это ограничение относится к самой ссылке или значению свойства, но не обязательно к внутреннему состоянию объекта, на который оно ссылается.
Swift изначально проектировался вокруг строгой проверки инициализации и явного разделения изменяемого и неизменяемого состояния. Такой подход позволяет обнаруживать ошибки использования объекта до завершения его корректного создания, а не оставлять свойства в неопределённом состоянии.
Различие между let-свойством и let-ссылкой на экземпляр класса также отражает фундаментальную семантику классов: сама ссылка может быть неизменяемой, тогда как объект по этой ссылке способен оставаться изменяемым.
Класс может требовать обязательную зависимость, например токен авторизации или идентификатор конфигурации. Если эту зависимость можно изменить после создания объекта, экземпляр может перейти в состояние, которое противоречит его инвариантам.
Неверное понимание правила приводит к двум типичным ошибкам: попытке присвоить let-свойство после инициализации и ошибочному ожиданию, что let сделает неизменяемым весь объект, на который ссылается свойство.
При создании экземпляра Swift проверяет, что каждое хранимое свойство получило значение до завершения designated-инициализатора. Свойство без значения по умолчанию можно один раз инициализировать в инициализаторе. После этого оно становится доступным только для чтения.
Если у let-свойства уже есть значение по умолчанию, оно считается инициализированным, поэтому повторно присвоить ему значение в инициализаторе нельзя. Унаследованное let-свойство инициализируется designated-инициализатором суперкласса; подкласс не может заново присвоить ему значение.
Convenience-инициализатор не может напрямую инициализировать хранимые свойства: он обязан делегировать создание экземпляра designated-инициализатору того же класса. В иерархии классов собственные свойства подкласса должны быть инициализированы согласно правилам цепочки инициализации до передачи управления суперклассу.
Неизменяемость относится к свойству как к хранимой ссылке или значению. Например, let service: Service запрещает заменить ссылку на другой экземпляр, но разрешает менять изменяемые свойства текущего Service, если сам Service — класс и его интерфейс это допускает.
Преимущество такого решения — защита инвариантов и более понятный жизненный цикл объекта. Ограничение состоит в том, что значение нужно знать во время инициализации; если его необходимо менять позднее, следует использовать var и отдельно контролировать допустимые переходы состояния.
Сервис должен работать с конкретным клиентом API на протяжении всего времени жизни объекта. Рассматривались два варианта.
Первый — объявить клиент как var. Это допускает замену клиента, но усложняет рассуждение о потокобезопасности и может привести к отправке части запросов через старую конфигурацию, а части — через новую.
Второй — объявить клиент как let и передать его через designated-инициализатор. Этот вариант фиксирует зависимость после создания сервиса и делает невозможной случайную замену ссылки. Если сам клиент является классом, его разрешённые внутренние изменения всё ещё возможны.
Для обязательной зависимости выбирается второй вариант: let-свойство явно выражает инвариант объекта — сервис всегда связан с тем же клиентом. Если замена действительно является частью жизненного цикла, её лучше оформить отдельным методом или создать новый экземпляр сервиса, а не ослаблять ограничение без необходимости.
let-свойству значение в нескольких ветвях инициализатора?Да, если компилятор может доказать, что до завершения инициализатора ровно один путь присваивает свойству значение. Повторное присваивание на одном возможном пути запрещено. Это часть проверки definite initialization, а не особое исключение для классов.
let-свойство?Нет. Свойство должно быть инициализировано инициализатором класса, которому оно принадлежит. Подкласс может использовать полученное значение, но не может повторно установить его для своего экземпляра. Если значение должно задаваться подклассом, это проектируют через параметры designated-инициализатора суперкласса или через другое расширяемое API.
let-свойство класса отличается от let-свойства-значения?Для свойства-значения let запрещает замену самого значения после инициализации. Для свойства, содержащего ссылку на класс, let запрещает заменить ссылку, но не запрещает изменять изменяемое состояние объекта по этой ссылке. Поэтому let фиксирует привязку, а не превращает ссылочный объект в неизменяемый.