Страницы

Поиск по вопросам

пятница, 5 апреля 2019 г.

Объяснение по fatal error: unexpectedly found nil while unwrapping an Optional value

На сегодняшний день у нас есть 6 практически одинаковых вопросов по сабжу. Давайте один раз напишем полный ответ по этому вопросу и будем в будущем на него ссылаться. Пока публикую под своим именем, если будут добавления/редактирования переставим на сообщество.


Ответ

Давайте один последний раз пройдемся по unexpectedly found nil while unwrapping an Optional value и больше никогда к нему не будем возвращаться.
Когда вы создаете переменные инстанса, swift от вас хочет, чтобы вы либо сразу им присвоили значения по дефолту, либо инициализировали их в init. Во многих случаях это нам не подходит, тогда мы объявляем свои переменные как optional.
var test:Int? // вопрос в конце, это объявление опциональной переменной
Что значит Optional? Это значит, что вместо просто переменной, например Int, мы получаем инумерацию из двух значений: none и some(Wrapped). Вот это Wrapped и есть значение нашей переменной.
Соответственно, если переменная не присвоена, то при попытке к ней обратиться, получим none т.е. nil.
Если же она содержит значение мы получим Optional(value)
Вот пример:
var test:Int? print(test) // напечатает 'nil' test = 42 print(test) // напечатает 'Optional(42)'
Далее, чтобы избавиться от этого Optional нам надо ее развернуть (unwrap), то есть сказать, что мы точно уверены, что там что то есть и мы хотим получить это самое что то. Другими словами, вместо Optional(value), получить value. Чтобы сделать принудительное разворачивание (forced unwrap) мы добавляем '!'. При этом, если переменная не присвоена, мы получим крэш - тот самый unexpectedly found nil while unwrapping an Optional value.
Пример:
var test:Int? print(test!) // крэш unexpectedly found nil и т.д.
var test2:Int? test2 = 42 print(test2!) // напечатает 42
Дальше, если мы хотим создать переменную как Optional, но точно знаем, что в нее будет что-то присвоено до того, как мы ее будем читать первый раз, мы можем ее объявить как Implicitly Unwrapped Optional Type, посредством добавления '!' в объявлении. В этом случае, она опять же будет Optional, но система будет ее принудительно разворачивать (force unwrap) при любом обращении к ней.
var test:Int! // '!' в конце, это обявление implicitly unwrapped optional print(test) // крэш unexpectedly found nil...
var test2:Int! test2 = 42 print(test2) // напечатает 42
А что делать, если мы понятия не имеем есть там что то в этой optional или нет?
var test:Int? if(test != nil) { print(test!) // развернутое значение } else { print(test) // nil }
Либо другой способ, который создает временную переменную с развернутым значением
var test:Int? if let unwrappedTest = test { print(unwrappedTest) } else { print("no value") }
Ну и если вам надо force unwrapped превратить обратно в Optional:
var test:Int! print(test as Optional) // напечатает nil
Optional chaining:
Есть еще такая ситуация, когда надо сделать цепочку вызовов, при этом где то в цепочке может содержаться опциональная перменная.
Вот такой пример дает apple:
Допустим есть класс Residence, и в нем есть переменная numberOfRooms; и есть класс Person, который имеет опциональную переменную типа Residence. Другими словами у человека может быть жилье, с каким то количеством комнат, а может и не быть жилья. То есть при попытке обратиться к человек-жилье-комнаты, мы можем наткнуться на nil вместо жилья.
class Person { var residence: Residence? }
class Residence { var numberOfRooms = 1 }
Теперь мы хотим создать новый Person, и узнать сколько комнат в его жилище (при этом residence может быть nil поскольку он Optional). Для этого мы говорим системе, что мы хотим делать с опциональным значением - либо оставлять его опциональным ('?') со всеми вытекающими последствиями, в частности получить nil вместо количества комнат; либо принудительно его разворачивать, при этом имея риск получить fatal error.
let john = Person() print(john.residence!.numberOfRooms) // крэш unexpectedly ... print(john.residence?.numberOfRooms) // напечатает nil
let tom = Person() tom.residence = Residence() print(tom.residence!.numberOfRooms) // напечатает 1 print(tom.residence?.numberOfRooms) // напечатает Optional(1)
Тут надо заметить, что хотя numberOfRooms не является опциональной, она в этой цепи становится таковой, поскольку цепь идет через опциональную residence?

Комментариев нет:

Отправить комментарий