Что означает «Неустранимая ошибка: неожиданно обнаружен ноль при развертывании необязательного значения»?
Моя программа Swift вылетает EXC_BAD_INSTRUCTION
из-за одной из следующих похожих ошибок. Что означает эта ошибка и как ее исправить?
Неустранимая ошибка: неожиданно обнаружено nil при разворачивании необязательного значения
или
Неустранимая ошибка: неожиданно обнаружен ноль при неявном разворачивании необязательного значения
Этот пост предназначен для того, чтобы собрать ответы на вопросы «неожиданно нулевые», чтобы их не было разрозненно и трудно найти. Не стесняйтесь добавлять свой ответ или редактировать существующий ответ вики.
Ответы
Этот ответ - вики сообщества . Если вы чувствуете, что его можно улучшить, не стесняйтесь редактировать !
Предыстория: что необязательно?
В Swift Optional<Wrapped>
это тип параметра : он может содержать любое значение из исходного («обернутого») типа или вообще не иметь значения (специальное значение nil
). Необязательное значение необходимо развернуть, прежде чем его можно будет использовать.
Необязательный - это универсальный тип , что означает, что Optional<Int>
и Optional<String>
являются разными типами - тип внутри <>
называется обернутым типом. Под капотом Optional - это перечисление с двумя регистрами: .some(Wrapped)
и .none
, где .none
эквивалентно nil
.
Необязательные параметры могут быть объявлены с использованием именованного типа Optional<T>
или (чаще всего) в виде сокращения с ?
суффиксом.
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
var aVerboseOptionalInt: Optional<Int> // equivalent to `Int?`
anOptionalInt = nil // now this variable contains nil instead of an integer
Optionals - это простой, но мощный инструмент для выражения ваших предположений при написании кода. Компилятор может использовать эту информацию, чтобы вы не ошиблись. Из языка программирования Swift :
Swift - это типобезопасный язык, что означает, что язык помогает вам четко понимать, с какими типами значений может работать ваш код. Если часть вашего кода требует a
String
, безопасность типов не позволяет вам передать егоInt
по ошибке. Точно так же безопасность типов предотвращает случайную передачу необязательногоString
в часть кода, для которой требуется необязательныйString
. Безопасность типов помогает выявлять и исправлять ошибки как можно раньше в процессе разработки.
Некоторые другие языки программирования также имеют общие типы параметров : например, Maybe в Haskell, option в Rust и optional в C ++ 17.
В языках программирования без типов опций конкретное «контрольное» значение часто используется для обозначения отсутствия допустимого значения. В Objective-C, например, nil
( нулевой указатель ) означает отсутствие объекта. Для примитивных типов, таких как int
нулевой указатель, использовать нельзя, поэтому вам потребуется либо отдельная переменная (например, value: Int
и isValid: Bool
), либо назначенное контрольное значение (например, -1
или INT_MIN
). Эти подходы подвержены ошибкам, потому что легко забыть проверить isValid
или проверить контрольное значение. Кроме того, если определенное значение выбрано в качестве дозорного, это означает, что оно больше не может рассматриваться как допустимое значение.
Типы опций, такие как Swift, Optional
решают эти проблемы, вводя специальное отдельное nil
значение (так что вам не нужно назначать контрольное значение) и используя сильную систему типов, чтобы компилятор мог помочь вам не забыть проверять ноль, когда это необходимо.
Почему я получил « фатальная ошибка: неожиданно обнаружил ноль при развертывании необязательного значения »?
Чтобы получить доступ к необязательному значению (если оно вообще есть), вам необходимо его развернуть . Необязательное значение можно развернуть безопасно или принудительно. Если вы принудительно развернете необязательный параметр, и он не имеет значения, ваша программа выйдет из строя с указанным выше сообщением.
Xcode покажет вам сбой, выделив строку кода. Проблема возникает в этой строке.
Этот сбой может произойти с двумя различными видами принудительного развертывания:
1. Явное принудительное разворачивание
Это делается с помощью !
оператора по желанию. Например:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Неустранимая ошибка: неожиданно обнаружено nil при развертывании необязательного значения
Как anOptionalString
и nil
здесь, вы получите сбой на линии, где вы ее принудительно развернете.
2. Неявно развернутые опции
Они определяются с помощью a !
, а не ?
после типа.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Предполагается, что эти дополнительные параметры содержат значение. Поэтому всякий раз, когда вы обращаетесь к неявно развернутому необязательному параметру, он будет автоматически развернут для вас принудительно. Если он не содержит значения, произойдет сбой.
print(optionalDouble) // <- CRASH
Неустранимая ошибка: неожиданно обнаружено значение nil при неявном развертывании необязательного значения
Чтобы определить, какая переменная вызвала сбой, вы можете удерживать нажатой ⌥кнопку мыши, чтобы показать определение, где вы можете найти дополнительный тип.
IBOutlets, в частности, обычно неявно разворачивают опционалы. Это связано с тем, что ваш xib или раскадровка будет связывать выходы во время выполнения после инициализации. Поэтому вы должны убедиться, что вы не получаете доступ к розеткам до их загрузки. Вы также должны проверить правильность соединений в вашем файле раскадровки / xib, иначе значения будут nil
во время выполнения и, следовательно, будут сбой, когда они неявно развернуты . При исправлении соединений попробуйте удалить строки кода, определяющие ваши розетки, а затем повторно подключите их.
Когда мне следует принудительно развернуть необязательный элемент?
Явное принудительное разворачивание
Как правило, никогда не следует принудительно развертывать необязательный элемент с помощью !
оператора. Могут быть случаи, когда использование !
допустимо, но вам следует использовать его только в том случае, если вы на 100% уверены, что необязательный параметр содержит значение.
Хотя может быть случай, когда вы можете использовать принудительное развертывание, поскольку вы точно знаете , что необязательный параметр содержит значение - нет ни одного места, где вы не могли бы безопасно развернуть этот необязательный элемент.
Неявно развернутые опции
Эти переменные разработаны таким образом, что вы можете отложить их назначение на более поздний срок в своем коде. Это ваша ответственность , чтобы убедиться , что они имеют значение , прежде чем получить доступ к ним. Однако, поскольку они включают принудительное развертывание, они по-прежнему небезопасны по своей сути - поскольку предполагают, что ваше значение не равно нулю, даже если присвоение nil допустимо.
Неявно развернутые опции следует использовать только в крайнем случае . Если вы можете использовать ленивую переменную или предоставить значение по умолчанию для переменной, вам следует сделать это вместо использования неявно развернутого необязательного параметра.
Тем не менее, есть несколько сценариев, в которых неявно развернутые опции являются полезными , и вы по-прежнему можете использовать различные способы их безопасного развертывания, как указано ниже, но вы всегда должны использовать их с должной осторожностью.
Как я могу безопасно работать с Optionals?
Самый простой способ проверить, содержит ли необязательный параметр значение, - это сравнить его с nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Однако в 99,9% случаев при работе с опциями вам действительно нужно получить доступ к значению, которое оно содержит, если оно вообще есть. Для этого вы можете использовать Optional Binding .
Необязательная привязка
Необязательная привязка позволяет вам проверить, содержит ли необязательный параметр значение - и позволяет назначить развернутое значение новой переменной или константе. Он использует синтаксис if let x = anOptional {...}
или if var x = anOptional {...}
, в зависимости от того, нужно ли вам изменить значение новой переменной после ее привязки.
Например:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
Это сначала проверяет, содержит ли необязательный параметр значение. Если это так , то «развернутое» значение присваивается новой переменной ( number
), которую вы затем можете свободно использовать, как если бы она была необязательной. Если необязательный параметр не содержит значения, то, как и следовало ожидать, будет вызвано предложение else.
Что хорошо в необязательной привязке, так это то, что вы можете развернуть несколько дополнительных опций одновременно. Вы можете просто разделить утверждения запятой. Оператор будет успешным, если все опции были развернуты.
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
Еще один изящный трюк заключается в том, что вы также можете использовать запятые для проверки определенного условия для значения после его развертывания.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
Единственная загвоздка при использовании необязательной привязки внутри оператора if заключается в том, что вы можете получить доступ к развернутому значению только из области действия оператора. Если вам нужен доступ к значению за пределами области действия оператора, вы можете использовать оператор защиты .
Оператор охранника позволяет определить условие для успеха - и текущая область будет только продолжать выполнение , если это условие выполнено. Они определены с помощью синтаксиса guard condition else {...}
.
Итак, чтобы использовать их с необязательной привязкой, вы можете сделать это:
guard let number = anOptionalInt else {
return
}
(Обратите внимание, что внутри тела защиты вы должны использовать один из операторов передачи управления , чтобы выйти из области действия выполняемого в данный момент кода).
Если anOptionalInt
содержит значение, оно будет развернуто и присвоено новой number
константе. Код после охранника продолжит выполнение. Если он не содержит значения - охранник выполнит код в скобках, что приведет к передаче управления, так что код сразу после него не будет выполнен.
Самое интересное в операторах защиты - это развернутое значение, которое теперь доступно для использования в коде, который следует за оператором (поскольку мы знаем, что будущий код может выполняться только в том случае, если необязательный параметр имеет значение). Это отлично подходит для устранения «пирамид гибели», созданных вложением нескольких операторов if.
Например:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Guard также поддерживает те же хитрые приемы, которые поддерживал оператор if, например, одновременное развертывание нескольких дополнительных опций и использование where
предложения.
Независимо от того, используете ли вы оператор if или guard, полностью зависит от того, требует ли какой-либо будущий код необязательный параметр, содержащий значение.
Оператор Nil Coalescing
Коалесцентный Оператор Nil это отличный вариант стенографии в тройном условном операторе , в первую очередь предназначен для преобразования в УСТРОЙСТВО , не являющиеся дополнительные опции. Он имеет синтаксис a ?? b
, где a
- необязательный тип и b
тот же тип a
(хотя обычно необязательный).
По сути, он позволяет вам сказать: «Если a
содержит значение, разверните его. Если нет, то возвращайся b
». Например, вы можете использовать это так:
let number = anOptionalInt ?? 0
Это определит number
константу Int
типа, которая будет либо содержать значение anOptionalInt
, если оно содержит значение, либо 0
иначе.
Это просто сокращение для:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Дополнительная цепочка
Вы можете использовать Optional Chaining , чтобы вызвать метод или получить доступ к свойству на optional. Это просто делается путем добавления к имени переменной суффикса ?
при ее использовании.
Например, предположим, что у нас есть переменная foo
типа необязательный Foo
экземпляр.
var foo : Foo?
Если мы хотим вызвать метод, foo
который ничего не возвращает, мы можем просто сделать:
foo?.doSomethingInteresting()
Если foo
содержит значение, для него будет вызываться этот метод. Если этого не произойдет, ничего страшного не произойдет - код просто продолжит выполнение.
(Это похоже на отправку сообщений nil
в Objective-C)
Поэтому это также можно использовать для установки свойств, а также для вызова методов. Например:
foo?.bar = Bar()
Опять же, ничего плохого здесь не произойдет, если foo
будет nil
. Ваш код просто продолжит выполнение.
Еще один изящный трюк, который позволяет выполнить необязательная цепочка, - это проверить, успешно ли было установлено свойство или вызван метод. Вы можете сделать это, сравнив возвращаемое значение с nil
.
(Это связано с тем, что будет возвращено необязательное значение, Void?
а не Void
метод, который ничего не возвращает)
Например:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
Однако все становится немного сложнее при попытке получить доступ к свойствам или вызвать методы, возвращающие значение. Поскольку foo
это необязательно, все, что из него возвращается, также будет необязательным. Чтобы справиться с этим, вы можете либо развернуть опциональные параметры, которые возвращаются с помощью одного из вышеуказанных методов, либо развернуть foo
себя перед доступом к методам или вызову методов, возвращающих значения.
Кроме того, как следует из названия, вы можете «связать» эти утверждения вместе. Это означает, что если foo
есть необязательное свойство baz
, у которого есть свойство, qux
вы можете написать следующее:
let optionalQux = foo?.baz?.qux
Опять же, поскольку foo
и baz
являются необязательными, значение, возвращаемое из qux
, всегда будет необязательным, независимо от того qux
, является ли оно необязательным.
map
а также flatMap
Часто недогружена функция с опциями является возможностью использовать map
и flatMap
функцию. Это позволяет вам применять необязательные преобразования к необязательным переменным. Если необязательный параметр имеет значение, вы можете применить к нему данное преобразование. Если он не имеет значения, он останется nil
.
Например, допустим, у вас есть необязательная строка:
let anOptionalString:String?
Применяя map
к нему функцию - мы можем использовать stringByAppendingString
функцию, чтобы объединить ее с другой строкой.
Поскольку stringByAppendingString
принимает необязательный строковый аргумент, мы не можем напрямую ввести нашу необязательную строку. Однако, используя map
, мы можем использовать allow stringByAppendingString
для использования, если anOptionalString
имеет значение.
Например:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Однако, если anOptionalString
не имеет значения, map
вернется nil
. Например:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
работает аналогично map
, за исключением того, что позволяет вам возвращать другой необязательный параметр из тела закрытия. Это означает, что вы можете ввести необязательный параметр в процесс, который требует необязательного ввода, но может вывести и сам по себе.
try!
Систему обработки ошибок Swift можно безопасно использовать с Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Если someThrowingFunc()
выдает ошибку, ошибка будет надежно зафиксирована в catch
блоке.
error
Константа вы видите в catch
блоке не была объявлена нами - это автоматически генерируется catch
.
Вы также можете заявить о error
себе, у него есть то преимущество, что вы можете преобразовать его в полезный формат, например:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
Использование try
этого способа - правильный способ попробовать, отловить и обработать ошибки, возникающие из-за бросания функций.
Там также try?
что поглотит ошибку:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Но система обработки ошибок Swift также предоставляет способ "принудительной попытки" try!
:
let result = try! someThrowingFunc()
Концепции, описанные в этом посте, также применимы и здесь: в случае возникновения ошибки приложение выйдет из строя.
Вам следует использовать его только в том try!
случае, если вы можете доказать, что его результат никогда не потерпит неудачу в вашем контексте - а это очень редко.
В большинстве случаев вы будете использовать полную систему Do-Try-Catch - и дополнительную try?
, в тех редких случаях, когда обработка ошибки не важна.
Ресурсы
TL; DR ответ
За очень немногими исключениями , это золотое правило:
Избегайте использования !
Объявить переменную optional ( ?
), а не неявно развернутыми опциями (IUO) ( !
)
Другими словами, лучше использовать:
var nameOfDaughter: String?
Вместо того:
var nameOfDaughter: String!
Разверните необязательную переменную с помощью if let
илиguard let
Либо разверните переменную следующим образом:
if let nameOfDaughter = nameOfDaughter {
print("My daughters name is: \(nameOfDaughter)")
}
Или вот так:
guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")
Этот ответ должен был быть кратким, для полного понимания прочтите принятый ответ
Ресурсы
Этот вопрос возникает ВСЕ ВРЕМЯ на SO. Это одна из первых проблем, с которой сталкиваются новые разработчики Swift.
Задний план:
Swift использует концепцию «Optionals» для работы со значениями, которые могут содержать значение или нет. В других языках, таких как C, вы можете сохранить значение 0 в переменной, чтобы указать, что она не содержит значения. Однако что, если 0 - допустимое значение? Тогда вы можете использовать -1. Что, если -1 - допустимое значение? И так далее.
Опции Swift позволяют вам настроить переменную любого типа, чтобы она содержала либо допустимое значение, либо отсутствие значения.
Вы ставите вопросительный знак после типа, когда объявляете переменную как имеющую значение (введите x или нет значения).
Необязательный элемент на самом деле является контейнером, который содержит либо переменную данного типа, либо ничего.
Необязательный параметр необходимо «развернуть», чтобы получить значение внутри.
Знак "!" Оператор - это оператор принудительного развертывания. В нем говорится: «Поверьте мне. Я знаю, что делаю. Я гарантирую, что при запуске этого кода переменная не будет содержать nil». Если вы ошибаетесь, вы терпите крах.
Если вы действительно не знаете, что делаете, избегайте символа "!" принудительно развернуть оператор. Вероятно, это самый большой источник сбоев для начинающих программистов Swift.
Как работать с опционами:
Есть много других более безопасных способов работы с опциями. Вот некоторые (не исчерпывающий список)
Вы можете использовать "необязательную привязку" или "если разрешить" сказать ", если этот необязательный параметр содержит значение, сохраните это значение в новой, необязательной переменной. Если необязательный параметр не содержит значения, пропустите текст этого оператора if. ".
Вот пример необязательного связывания с нашим foo
optional:
if let newFoo = foo //If let is called optional binding. {
print("foo is not nil")
} else {
print("foo is nil")
}
Обратите внимание, что переменная, которую вы определяете при использовании необязательного назначения ставок, существует только (находится «в области действия») в теле оператора if.
В качестве альтернативы вы можете использовать оператор защиты, который позволяет вам выйти из функции, если переменная равна нулю:
func aFunc(foo: Int?) {
guard let newFoo = input else { return }
//For the rest of the function newFoo is a non-optional var
}
Операторы защиты были добавлены в Swift 2. Guard позволяет сохранить «золотой путь» в коде и избежать постоянно растущих уровней вложенных if, которые иногда возникают в результате использования необязательной привязки «if let».
Также существует конструкция, называемая «оператором объединения nil». Он принимает форму «optional_var ?? replace_val». Он возвращает необязательную переменную того же типа, что и данные, содержащиеся в необязательной. Если необязательный параметр содержит nil, он возвращает значение выражения после "??" символ.
Итак, вы можете использовать такой код:
let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")
Вы также можете использовать обработку ошибок try / catch или guard, но обычно один из других методов, описанных выше, более чист.
РЕДАКТИРОВАТЬ:
Еще одна, немного более тонкая проблема с необязательными параметрами - это «неявно развернутые необязательные параметры. Когда мы объявляем foo, мы могли бы сказать:
var foo: String!
В этом случае foo по-прежнему является необязательным, но вам не нужно разворачивать его, чтобы ссылаться на него. Это означает, что каждый раз, когда вы пытаетесь сослаться на foo, вы вылетаете, если оно равно nil.
Итак, этот код:
var foo: String!
let upperFoo = foo.capitalizedString
Произойдет сбой при ссылке на свойство capitalizedString foo, даже если мы не выполняем принудительное развертывание foo. печать выглядит хорошо, но это не так.
Таким образом, вы должны быть очень осторожны с неявно развернутыми опциями. (и, возможно, даже полностью избегайте их, пока не получите твердое представление о дополнительных возможностях.)
Итог: когда вы впервые изучаете Swift, сделайте вид, что "!" характер не является частью языка. Это может доставить вам неприятности.
Поскольку приведенные выше ответы четко объясняют, как безопасно играть с Optionals. Я постараюсь быстро объяснить, что такое Optionals.
Другой способ объявить необязательную переменную -
var i : Optional<Int>
И необязательный тип - это не что иное, как перечисление с двумя регистрами, т.е.
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
.
.
.
}
Итак, чтобы присвоить нашей переменной «i» ноль. Мы можем сделать
var i = Optional<Int>.none
или присвоить значение, мы передадим какое-то значение
var i = Optional<Int>.some(28)
Согласно Свифт, «ноль» - это отсутствие ценности. И для создания экземпляра, инициализированного с помощью, nil
мы должны соответствовать названному протоколу ExpressibleByNilLiteral
и, если вы догадались, отлично, только Optionals
согласование ExpressibleByNilLiteral
с другими типами и согласование с ними не рекомендуется.
ExpressibleByNilLiteral
имеет единственный вызываемый метод, init(nilLiteral:)
который инициализирует instace значением nil. Обычно вы не вызываете этот метод, и, согласно быстрой документации, не рекомендуется вызывать этот инициализатор напрямую, поскольку компилятор вызывает его всякий раз, когда вы инициализируете необязательный тип с помощью nil
литерала.
Даже мне приходится оборачиваться (без каламбура) вокруг Optionals: D Happy Swfting All .
Во-первых, вы должны знать, что такое необязательное значение. Вы можете перейти к Swift Programming Language для получения подробной информации.
Во-вторых, вы должны знать, что необязательное значение имеет два статуса. Один - это полное значение, а другой - нулевое значение. Поэтому, прежде чем реализовывать необязательное значение, вы должны проверить его состояние.
Вы можете использовать if let ...
или guard let ... else
и так далее.
Другой способ: если вы не хотите проверять состояние переменной перед реализацией, вы также можете использовать var buildingName = buildingName ?? "buildingName"
вместо этого.
Однажды у меня была эта ошибка, когда я пытался установить значения своих выходов из метода подготовки к переходу следующим образом:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// This line pops up the error
destination.nameLabel.text = item.name
}
}
}
Затем я обнаружил, что не могу установить значения выходов целевого контроллера, потому что контроллер еще не был загружен или инициализирован.
Итак, я решил это так:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// Created this method in the destination Controller to update its outlets after it's being initialized and loaded
destination.updateView(itemData: item)
}
}
}
Конечный контроллер:
// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""
// Outlets
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
nameLabel.text = name
}
func updateView(itemDate: ObjectModel) {
name = itemDate.name
}
Я надеюсь, что этот ответ поможет любому, кто сталкивается с той же проблемой, поскольку я обнаружил, что отмеченный ответ является отличным ресурсом для понимания опций и того, как они работают, но не касался непосредственно самой проблемы.
В основном вы пытались использовать значение nil в местах, где Swift допускает только значения, отличные от nil, сообщая компилятору доверять вам, что там никогда не будет значения nil, что позволяет вашему приложению компилироваться.
Есть несколько сценариев, которые приводят к такой фатальной ошибке:
принудительное развертывание:
let user = someVariable!
Если
someVariable
равно нулю, вы получите сбой. Выполнив принудительное развертывание, вы переместили ответственность за проверку nil с компилятора на вас, в основном путем принудительного развертывания, вы гарантируете компилятору, что у вас никогда не будет там нулевых значений. И угадайте, что произойдет, если каким-то образом значение nil оканчивается наsomeVariable
?Решение? Используйте необязательную привязку (также известную как if-let), выполните там обработку переменных:
if user = someVariable { // do your stuff }
форсированные (вниз) забросы:
let myRectangle = someShape as! Rectangle
Здесь путем принудительного приведения вы говорите компилятору больше не беспокоиться, так как у вас всегда будет
Rectangle
экземпляр. И пока это сохраняется, вам не о чем беспокоиться. Проблемы начинаются, когда вы или ваши коллеги из проекта начинаете распространять непрямоугольные значения.Решение? Используйте необязательную привязку (также известную как if-let), выполните там обработку переменных:
if let myRectangle = someShape as? Rectangle { // yay, I have a rectangle }
Неявно развернутые опции. Предположим, у вас есть следующее определение класса:
class User { var name: String! init() { name = "(unnamed)" } func nicerName() { return "Mr/Ms " + name } }
Теперь, если никто не испортит
name
свойство, установив для него значениеnil
, оно будет работать должным образом, однако, еслиUser
оно инициализировано из JSON, в котором отсутствуетname
ключ, вы получите фатальную ошибку при попытке использовать свойство.Решение? Не используйте их :) Если вы не уверены на 102%, что свойство всегда будет иметь ненулевое значение к тому моменту, когда его нужно будет использовать. В большинстве случаев преобразование в необязательный или необязательный будет работать. Если сделать его необязательным, компилятор также поможет вам, сообщив пропущенные пути кода, присвоив значение этому свойству.
Не подключенные или еще не подключенные розетки. Это частный случай сценария №3. В основном у вас есть класс, загруженный XIB, который вы хотите использовать.
class SignInViewController: UIViewController { @IBOutlet var emailTextField: UITextField! }
Теперь, если вы пропустили подключение розетки из редактора XIB, приложение выйдет из строя, как только вы захотите использовать розетку. Решение? Убедитесь, что все розетки подключены. Или используйте
?
оператор на них:emailTextField?.text = "[email protected]"
. Или объявите выход как необязательный, хотя в этом случае компилятор заставит вас развернуть его по всему коду.Значения поступают из Objective-C и не имеют аннотаций обнуляемости. Предположим, у нас есть следующий класс Objective-C:
@interface MyUser: NSObject @property NSString *name; @end
Теперь, если аннотации, допускающие значение NULL, не указаны (явно или через
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
), тоname
свойство будет импортировано в Swift какString!
(IUO - неявно развернутый необязательно). Как только какой-нибудь быстрый код захочет использовать это значение, он выйдет из строя, еслиname
будет nil.Решение? Добавьте аннотации обнуления в свой код Objective-C. Однако будьте осторожны, компилятор Objective-C немного снисходителен, когда дело доходит до допустимости значений NULL, вы можете получить значения nil, даже если вы явно пометили их как
nonnull
.
Это более важный комментарий, и именно поэтому неявно развернутые опции могут быть обманчивыми, когда дело доходит до nil
значений отладки .
Подумайте о следующем коде: он компилируется без ошибок / предупреждений:
c1.address.city = c3.address.city
Тем не менее, во время выполнения он выдает следующую ошибку: Неустранимая ошибка: неожиданно обнаружен ноль при разворачивании необязательного значения
Вы можете сказать мне, что это за объект nil
?
Вы не можете!
Полный код будет:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c3 = BadContact()
c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct BadContact {
var address : Address!
}
struct Address {
var city : String
}
Короче говоря, используя var address : Address!
вы скрываете возможность того, что переменная может быть nil
от других читателей. И когда он вылетает, вы думаете: «Что за черт ?! мой address
не является обязательным, так почему я рушусь?!.
Следовательно, лучше писать так:
c1.address.city = c2.address!.city // ERROR: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Можете ли вы теперь сказать мне, что это за объект nil
?
На этот раз код стал для вас более понятным. Вы можете рационализировать и подумать, что, вероятно, это address
параметр, который был принудительно развернут.
Полный код будет:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c2 = GoodContact()
c1.address.city = c2.address!.city
c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
if let city = c2.address?.city { // safest approach. But that's not what I'm talking about here.
c1.address.city = city
}
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct GoodContact {
var address : Address?
}
struct Address {
var city : String
}
Ошибки EXC_BAD_INSTRUCTION
и fatal error: unexpectedly found nil while implicitly unwrapping an Optional value
появляются чаще всего, когда вы объявили @IBOutlet
, но не подключены к раскадровке .
Вы также должны узнать о том, как работают Optionals , упомянутые в других ответах, но это единственный раз, который в основном мне кажется.
Если вы получаете эту ошибку в CollectionView, попробуйте также создать файл CustomCell и Custom xib.
добавьте этот код в ViewDidLoad () на mainVC.
let nib = UINib(nibName: "CustomnibName", bundle: nil)
self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
Я столкнулся с этой ошибкой при переходе от контроллера табличного представления к контроллеру представления, потому что я забыл указать имя настраиваемого класса для контроллера представления в основной раскадровке.
Что-то простое, что стоит проверить, все ли в порядке