Что означает объявление свойства с восклицательным знаком в Swift?
Что проверяют
Понимаете ли вы, что восклицательный знак в объявлении это не отмена опциональности, а опционал с автоматическим развёртыванием, и назовёте ли случаи, где он оправдан.
Как отвечать
Запись var titleLabel: UILabel! объявляет неявно развёрнутый опционал. Тип остаётся опциональным: переменная может быть nil, по умолчанию она nil, а компилятор сам подставляет принудительное развёртывание там, где по контексту требуется не-опционал. Обращение при nil это падение в рантайме.
Где такое оправданно:
- аутлеты интерфейса: на момент создания контроллера иерархия вью ещё не загружена, значение проставляется при загрузке из storyboard или xib;
- разрыв двухфазной инициализации, когда зависимость выставляется сразу после
init, а в самом инициализаторе её взять неоткуда; - свойство, которое гарантированно заполняется до первого обращения по жизненному циклу экрана.
Почти везде лучше обычный опционал с guard let, внедрение через инициализатор или lazy var, который вычисляется при первом обращении. Полезно добавить, что такой тип допустим только на верхнем уровне объявления, типом элемента коллекции его не сделать. И что восклицательный знак в выражении это другое: value!, as!, try! это уже явное обещание разработчика.
Где спотыкаются
Уверены, что это просто не-опционал. Он опционал, и там, где контекст допускает опциональное значение, развёртывание не срабатывает: вывод такого свойства в лог даст обёртку вокруг значения, а не само значение, и сравнение с nil остаётся законным.
Помечают восклицательным знаком все свойства, лишь бы не писать инициализатор. Получается класс, который компилируется, но падает на неочевидном порядке вызовов, причём падает у пользователя, а не в отладчике.
Трогают аутлеты слишком рано. Обращение к аутлету в инициализаторе или с другого экрана до показа контроллера роняет приложение, потому что иерархия вью ещё не создана. Работать с ними можно начиная с viewDidLoad.