Ответ 1
Вся концепция вашего кода неверна.
-
В
prepareForSegue
вы не должны ссылаться на интерфейс контроллера представления назначения, поскольку он не имеет интерфейса.viewDidLoad
еще не запущен; контроллер просмотра не имеет вида, нет выходов, ничего нет. -
Ваш ленивый инициализатор для свойства метки не должен также добавлять метку к интерфейсу. Он должен просто сделать ярлык и вернуть его.
Другие вещи, которые нужно знать:
-
Обращаясь к контроллеру представления
view
, прежде чем он имеет представление, он заставит это представление загружаться преждевременно. Выполнение этого неправильного действия может фактически вызвать просмотр нагрузки дважды, что может иметь ужасные последствия. -
Единственный способ задать диспетчеру просмотра, независимо от того, загрузил ли его представление, не заставляя преждевременно загружать представление, - это
isViewLoaded()
.
Правильная процедура для того, что вы хотите сделать:
-
В
prepareForSegue
присвойте строке имени свойствуname
и все. Он может иметь наблюдателя, но этот наблюдатель не должен ссылаться наview
, если в это время мы не имеемview
, потому что это приведет к тому, чтоview
будет загружаться преждевременно. -
В
viewDidLoad
, тогда и только тогда у нас есть представление, и теперь вы можете начать заполнять интерфейс.viewDidLoad
должен создать ярлык, поместить его в интерфейс, затем выбрать свойствоname
и присвоить его метке.
ИЗМЕНИТЬ
Теперь, сказав все это... Что это касается вашего первоначального вопроса? Как то, что вы делаете неправильно, объясняет, что делает Swift, и что делает Swift неправильно?
Чтобы увидеть ответ, просто поставьте точку останова на:
lazy var testLabel: UILabel = {
NSLog("testLabel self = \(self)") // breakpoint here
// ...
Что вы увидите, так это то, что из-за того, как вы структурировали свой код, мы возвращаем значение testLabel
дважды рекурсивно. Здесь стек вызовов немного упрощен:
prepareForSegue
name.didset
testLabel.getter -> *
viewDidLoad
testLabel.getter -> *
Геттер testLabel
относится к контроллеру представления view
, который вызывает загрузку представления контроллера представления, и поэтому его вызов viewDidLoad
вызывается и вызывает повторный вызов геттера testLabel
.
Обратите внимание, что получатель не просто вызывается дважды в последовательности. Он вызывается дважды рекурсивно: он сам по себе сам называет себя.
Именно эта рекурсия, с которой Свифт не защищается. Если сеттер просто вызывался дважды подряд, ленивый инициализатор не был бы вызван во второй раз. Но в вашем случае это рекурсивно. Так что правда, что второй раз, ленивый инициализатор никогда не запускался раньше. Он был запущен, но он так и не был завершен. Таким образом, Свифт оправдан в запуске его сейчас, что означает, что он снова запускает его.
Итак, в некотором смысле, да, вы поймали Свифта с его штанами, но то, что вам нужно было сделать, чтобы это произошло, настолько возмутительно, что его можно оправдано назвать вашей собственной ошибкой. Это может быть ошибка Swift, но если это так, это ошибка, которая никогда не должна встречаться в реальной жизни.
EDIT:
В видео WWDC 2016 на Swift и concurrency Apple явно говорит об этом. В Swift 1 и 2 и даже в Swift 3 переменные экземпляра lazy
не являются атомарными, и поэтому инициализатор может запускаться дважды, если он вызван из двух контекстов одновременно - это именно то, что делает ваш код.