Вчера я работал над приложением для курса разработки iOS, который я посещаю, и около часа пытался найти ошибку. Эта ошибка связана с функцией @IBAction вместе с UIStoryboardSegue, и я уверен, что многие другие разработчики тоже сталкивались с ней.

Вот мой обзор этого, а также решение для его решения, если вы столкнетесь с этим сами! Я надеюсь, это поможет вам!

Ошибка и описание проекта

Не торопясь и дав себе некоторое время, чтобы выяснить, в чем проблема, я, наконец, понял, что она была вызвана функцией @IBAction, связанной с UIButton, которая также была напрямую связана с UIStoryboardSegue.

Ошибка заключалась в следующем:

  1. У меня было 2 контроллера просмотра. У первого контроллера представления был UIButton, при касании которого вызывался переход (который был напрямую связан с касанием UIButton), который показывал второй контроллер представления. Этот второй контроллер представления должен был получать пользовательский ввод, введенный в UITextField из первого контроллера представления, и отображать его.
  2. Кнопка UIButton, запускающая переход, также была связана с функцией @IBAction, которая должна была выполнить некоторую проверку перед передачей пользовательского ввода второму контроллеру представления через переход. Если пользовательский ввод прошел проверку, предполагалось, что он будет передан второму контроллеру представления, который затем сохранит это значение в одном из своих необязательных свойств, которые будут отображаться на экране. В случае сбоя пользователю должно было быть представлено сообщение об ошибке.
  3. Когда кнопка была нажата и пользовательский ввод оценивался функцией проверки кнопки, второй контроллер представления не отображался, и пользователю не отображалось сообщение об ошибке. Вместо этого нажатие кнопки мгновенно приводило к сбою приложения со следующим сообщением об ошибке: Поток 1: Неустранимая ошибка: неожиданно найдено nil при неявном развертывании дополнительного значения.
  4. Мой второй контроллер представления выглядел примерно так после сбоя (это просто пример, который я придумал, а не мой реальный проект):

5. Мой первый View Controller выглядел примерно так:

6. А вот и рассматриваемая кнопка (опять же, которая связана как с функцией SumSegue, так и с функцией @IBAction выше) в пользовательском интерфейсе приложения:

7. Желаемое поведение таково: кнопка в ButtonViewController нажата, сумма 2 + 2 передается в свойство суммы SumViewController, метод update(with:) SumViewController обновляет текст SumLabel со значением свойства суммы , и сумма отображается на экране следующим образом:

Что вызвало ошибку и как ее исправить?

Эта проблема коренится в том, что метод update(with:) вызывается в методе viewDidLoad() SumViewController, а значение свойства sum SumViewController передается в метод update(with:).

Это проблема, потому что значение нашего свойства sum помечено как Int!, что означает, что мы обещаем Swift, что свойство будет иметь значение к тому времени, когда оно понадобится. Однако мы нарушили это обещание, так как вызываем update(with:) до того, как наше свойство sum получит значение, отсюда и сбой.

Но почему свойство sum в SumViewController не имеет значения? Разве мы не присваиваем ему значение в методе prepare(for:sender:) ButtonViewController? Ну, мы пытаемся, но у нас не получается. Это связано с тем, что следующий блок кода никогда не запускается, когда мы нажимаем на нашу кнопку, поэтому свойство суммы ButtonViewController никогда не обновляется значением. Вместо этого в значение свойства SumViewController передается значение nil:

Почему этот блок кода никогда не запускается? Ну, судя по проведенному мной тестированию, кажется, что связывание перехода непосредственно с UIButton говорит UIKit полностью игнорировать любые функции @IBAction, которые связаны с этим UIButton, и вместо этого выполнять переход, с которым он связан.

Вот как я это понял: я поместил оператор печати в функцию @IBAction, связанную с кнопкой. Этот оператор печати никогда не выводился на консоль после нажатия кнопки, но ранее упомянутое сообщение об ошибке было выдано после того, как iOS попыталась выполнить переход.

Итак, как нам разделить функциональность кнопки Segue и @IBAction, чтобы свойство суммы ButtonViewController обновлялось и передавалось в наш SumViewController? Все, что нам нужно сделать, это удалить наш текущий переход и связать новый переход непосредственно с нашим ButtonViewController в интерфейсном конструкторе, а не с нашей кнопкой:

Теперь мне все еще нужно убедиться, что идентификатор перехода соответствует идентификатору, на который мы уже ссылались в нашем ButtonViewController, но как только это будет сделано, ошибка будет устранена! Теперь функция @IBAction нашей кнопки будет вызываться, как и ожидалось, свойство sum в ButtonViewController будет установлено на правильное значение, и это значение затем будет передано в SumViewController.

Это объяснение было довольно подробным, особенно для проблемы с таким простым решением, но для меня это был момент огромной лампочки, и каждая деталь дает важный контекст для работы над этой ошибкой. Я также подумал, что дополнительные подробности того стоят, потому что я не могу найти ни одного упоминания @IBAction в документации Apple для разработчиков, поэтому я даже не уверен, упоминается ли эта концепция где-либо в документации UIKit.

Если это помогло вам, дайте мне знать! Если у вас есть какие-либо вопросы, напишите комментарий, чтобы увидеть, есть ли у меня ответы :)