Что означает «Неустранимая ошибка: неожиданно найденный ноль при развертывании необязательного значения»?


415

Моя программа Swift аварийно завершает работу с EXC_BAD_INSTRUCTIONодной из следующих подобных ошибок. Что означает эта ошибка и как ее исправить?

Неустранимая ошибка: неожиданно обнаружен ноль при развертывании необязательного значения

или

Неустранимая ошибка: неожиданно обнаружен ноль при неявном развертывании необязательного значения


Этот пост предназначен для сбора ответов на «неожиданно найденные ноль» проблемы, чтобы они не были разбросаны и их было сложно найти. Не стесняйтесь добавлять свой собственный ответ или редактировать существующий вики-ответ.


8
@RobertColumbia, это пара вопросов и ответов сообщества Wiki с обширной документацией по проблеме, я не думаю, что этот вопрос следует закрыть из-за отсутствия MCVE.
JAL

Создайте переменную, как это [var nameOfDaughter: String? ] и используйте эту переменную следующим образом [if let _ = nameOfDaughter {}] - лучший подход.
iOS

Ответы:


674

Этот ответ вики сообщества . Если вы чувствуете, что это может быть сделано лучше, не стесняйтесь редактировать это !

Фон: что необязательно?

В Swift Optional- это универсальный тип, который может содержать значение (любого типа) или вообще не иметь значения.

Во многих других языках программирования определенное значение «страж» часто используется для указания на отсутствие значения . Например, nilв Objective-C ( нулевой указатель ) указывает на отсутствие объекта. Но это становится более сложным при работе с примитивными типами - следует -1использовать, чтобы указать отсутствие целого числа, или, возможно INT_MIN, или какого-то другого целого числа? Если какое-либо конкретное значение выбрано для обозначения «нет целого числа», это означает, что оно больше не может рассматриваться как допустимое значение.

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

В Swift любой тип можно сделать необязательным . Необязательное значение может принимать любое значение из исходного типа или специальное значение nil.

Необязательные параметры определяются с ?суффиксом типа:

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided

Отсутствие значения в опциональном обозначается nil:

anOptionalInt = nil

(Обратите внимание, что это nilне то же самое, что nilв Objective-C. В Objective-C nil- отсутствие действительного указателя объекта ; в Swift Optional не ограничиваются объектами / ссылочными типами. Optional ведет себя подобно Haskell Maybe .)


Почему я получил « фатальную ошибку: неожиданно обнаружил ноль при развертывании необязательного значения »?

Чтобы получить доступ к необязательному значению (если оно вообще есть), вам необходимо развернуть его. Необязательное значение может быть развернуто безопасно или принудительно. Если вы принудительно развернете необязательный параметр, у которого не было значения, ваша программа вылетит с сообщением, приведенным выше.

Xcode покажет вам сбой, выделив строку кода. Проблема возникает на этой линии.

разрушенная линия

Этот сбой может произойти с двумя различными видами принудительного развертывания:

1. Явное развертывание силы

Это делается с !оператором по желанию. Например:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

Неустранимая ошибка: неожиданно обнаружен ноль при развертывании необязательного значения

Как anOptionalStringи nilздесь, вы получите сбой на линии, где вы принудительно разверните его.

2. Неявно развернутые необязательные

Они определены с !, а не ?после типа.

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

Предполагается, что эти опции содержат значение. Поэтому всякий раз, когда вы получаете доступ к неявно развернутому необязательному файлу, он будет автоматически развернут для вас. Если он не содержит значения, он потерпит крах.

print(optionalDouble) // <- CRASH

Неустранимая ошибка: неожиданно обнаружен ноль при неявном развертывании необязательного значения

Чтобы определить, какая переменная вызвала сбой, вы можете удерживать нажатой , чтобы показать определение, где вы можете найти необязательный тип.

В частности, IBOutlets обычно являются неявно развернутыми опциями. Это потому, что ваш xib или раскадровка свяжут розетки во время выполнения после инициализации. Поэтому вы должны убедиться, что у вас нет доступа к розеткам до их загрузки. Вы также должны проверить правильность соединений в вашем файле раскадровки / xib, в противном случае значения будут nilво время выполнения и, следовательно, сбой, когда они неявно развернуты. , При исправлении соединений попробуйте удалить строки кода, которые определяют ваши розетки, а затем снова подключить их.


Когда я должен когда-либо принудительно развернуть Дополнительный?

Явная распаковка силы

Как правило, вы никогда не должны явно развертывать опционально с !оператором. Могут быть случаи, когда использование !приемлемо, но вы должны использовать его только в том случае, если вы на 100% уверены, что необязательный параметр содержит значение.

Хотя может быть случай, когда вы можете использовать принудительное развертывание, как вы знаете, для факта, что необязательный параметр содержит значение - нет ни одного места, где вы не можете безопасно развернуть это необязательное.

Неявно развернутые необязательные

Эти переменные разработаны таким образом, что вы можете отложить их назначение до следующего момента в вашем коде. Это ваша ответственность , чтобы убедиться , что они имеют значение , прежде чем получить доступ к ним. Однако, поскольку они включают принудительное развертывание, они по-прежнему небезопасны - поскольку они предполагают, что ваше значение не равно нулю, даже если присвоение nil допустимо.

Вы должны использовать только неявно развернутые опции как последнее средство . Если вы можете использовать переменную lazy или предоставить значение по умолчанию для переменной - вы должны сделать это вместо использования неявно развернутого необязательного параметра.

Тем не менее, есть несколько сценариев, в которых неявно развернутые дополнительные компоненты полезны , и вы все еще можете использовать различные способы их безопасного развертывания, как указано ниже, но вы всегда должны использовать их с должной осторожностью.


Как я могу безопасно иметь дело с Опциональными?

Самый простой способ проверить, содержит ли необязательное значение значение, - сравнить его с 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")
}

Это сначала проверяет, что необязательное содержит значение. Если это так , тогда значение unwrapped присваивается новой переменной ( 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константе. Код после охраны продолжит выполнение. Если оно не содержит значения - охранник выполнит код в скобках, что приведет к передаче управления, так что код сразу после этого не будет выполнен.

Реальная изящная вещь в операторах guard - это то, что теперь развернутое значение доступно для использования в коде, который следует за оператором (поскольку мы знаем, что будущий код может выполняться только в том случае, если необязательное значение имеет значение). Это отлично подходит для устранения «пирамид гибели», созданных путем вложения нескольких операторов if.

Например:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

Охранники также поддерживают те же хитрые приемы, которые поддерживал оператор if, такие как одновременное развертывание нескольких дополнительных опций и использование whereпредложения.

Используете ли вы оператор if или guard, полностью зависит от того, требует ли какой-либо будущий код необязательного значения.

Оператор слияния

Коалесцентный Оператор Nil это отличный вариант стенографии в тройном условном операторе , в первую очередь предназначен для преобразования в УСТРОЙСТВО , не являющиеся дополнительные опции. Он имеет синтаксис a ?? b, где aэто необязательный тип и bтот же тип, что и a(хотя обычно не необязательный).

По сути, он позволяет вам сказать: «Если aсодержит значение, разверните его. Если этого не произойдет, тогда вернитесь bвместо этого ». Например, вы можете использовать это так:

let number = anOptionalInt ?? 0

Это определит numberконстанту Intтипа, которая будет либо содержать значение anOptionalInt, если оно содержит значение, либо 0иным образом.

Это просто сокращение для:

let number = anOptionalInt != nil ? anOptionalInt! : 0

Опциональная цепочка

Вы можете использовать Optional Chaining , чтобы вызвать метод или получить доступ к свойству по желанию. Это просто делается путем добавления суффикса к имени переменной при его ?использовании.

Например, допустим, у нас есть переменная 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?, в тех редких случаях, когда обработка ошибки не важна.


Ресурсы


87
Лично я хотел бы поблагодарить вас за то, что вы приложили усилия, чтобы написать все это. Я чувствую, что это, безусловно, будет полезно как начинающим, так и экспертам, использующим swift.
Пранав Вадхва

15
Я рад, что вы нашли это полезным. Этот ответ - вики сообщества, так что это сотрудничество между многими людьми (пока 7)!
Jtbandes

1
Эта ошибка периодически в моем случае. Какие-либо предложения? Код в stackoverflow.com/questions/50933681/…
py_ios_dev

1
Возможно, пришло время обновить этот ответ, чтобы использовать compactMap()вместо flatMap().
Николас Миари

так что я думаю, что лучшее и короткое решение - это оператор Nil Coalescing, верно?
Мехдигриш

65

TL; DR ответ

За очень немногими исключениями это правило является золотым:

Избегайте использования !

Объявите переменную ?option ( ), а не неявно развернутые optionals (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)")

Этот ответ должен был быть кратким, для полного понимания прочитайте принятый ответ


Ресурсы


40

Этот вопрос возникает все время на SO. Это одна из первых вещей, с которыми борются новые разработчики Swift.

Фон:

Swift использует концепцию «Optionals» для работы со значениями, которые могут содержать значение или нет. В других языках, таких как C, вы можете хранить значение 0 в переменной, чтобы указать, что оно не содержит значения. Однако что, если 0 является допустимым значением? Тогда вы можете использовать -1. Что если -1 является допустимым значением? И так далее.

Опции Swift позволяют вам установить переменную любого типа, которая будет содержать либо действительное значение, либо отсутствие значения.

Вы ставите вопросительный знак после типа, когда объявляете значение переменной (тип x или нет значения).

Необязательным на самом деле является контейнер, который содержит либо переменную заданного типа, либо ничего.

Необязательный должен быть "развернут", чтобы получить значение внутри.

"!" оператор является оператором "развернуть силой". Он говорит: «Поверьте мне. Я знаю, что я делаю. Я гарантирую, что при запуске этого кода переменная не будет содержать ноль». Если вы не правы, вы терпите крах.

Если вы действительно не знаете, что делаете, избегайте "!" Принудительно развернуть оператор. Вероятно, это самый большой источник сбоев для начинающих программистов Swift.

Как бороться с опционами:

Есть много других способов иметь дело с опциями, которые безопаснее. Вот некоторые (не исчерпывающий список)

Вы можете использовать «необязательное связывание» или «если позволено», чтобы сказать «если это необязательное содержит значение, сохраните это значение в новой, необязательной переменной. Если необязательное не содержит значения, пропустите основную часть этого оператора if». ».

Вот пример необязательного связывания с нашим fooнеобязательным:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

Обратите внимание, что переменная, которую вы определяете при использовании необязательного связывания, существует (находится только в области видимости) в теле оператора if.

В качестве альтернативы вы можете использовать оператор guard, который позволяет вам выйти из функции, если переменная равна nil:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

Операторы Guard были добавлены в Swift 2. Guard позволяет вам сохранять «золотой путь» в вашем коде и избегать постоянно увеличивающихся уровней вложенных if, которые иногда возникают в результате использования необязательного связывания «if let».

Существует также конструкция, называемая «оператор слияния ноль». Он принимает форму «необязательный_вар», «замена_вал». Он возвращает необязательную переменную того же типа, что и данные, содержащиеся в необязательной переменной. Если необязательный параметр содержит nil, он возвращает значение выражения после «??» символ.

Таким образом, вы можете использовать такой код:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

Вы также можете использовать try / catch или обработку ошибок guard, но, как правило, один из других методов выше - чище.

РЕДАКТИРОВАТЬ:

Другой, несколько более тонкий хитрость с опциями - это «неявно развернутые опционы». Когда мы объявляем foo, мы можем сказать:

var foo: String!

В этом случае foo все еще не обязателен, но вам не нужно разворачивать его, чтобы ссылаться на него. Это означает, что каждый раз, когда вы пытаетесь сослаться на foo, вы теряете значение, если оно равно нулю.

Итак, этот код:

var foo: String!


let upperFoo = foo.capitalizedString

Сбой при ссылке на свойство foo capitalizedString, даже если мы не распаковываем foo принудительно. отпечаток выглядит хорошо, но это не так.

Таким образом, вы хотите быть очень осторожным с неявно развернутыми опциями. (и, возможно, даже избегайте их полностью, пока у вас не будет четкого понимания дополнительных возможностей.)

Итог: когда вы впервые изучаете Swift, сделайте вид «!» характер не является частью языка. Это может привести к неприятностям.


7
Вы должны рассмотреть возможность сделать это публичной вики.
Vacawama

3
Отредактируйте свой ответ, и под полем редактирования справа есть флажок сообщества вики . Вы больше не будете получать репутацию за ответ, но я знаю, что в любом случае это не явный захват репутации.
Vacawama

6
Учитывая, что этот вопрос задают миллион раз на сайте, если бы я собирался выделить время, чтобы опубликовать вопрос с самостоятельным ответом в качестве канонического ответа на вопрос, я надеюсь, что ответ будет гораздо более полным, чем этот. Там нет ничего о guardпунктах. Там нет ничего о использовании if varкоторого так же, как конструкция if let. В предложениях нет ничего такого, о whereчем стоит упомянуть, когда мы говорим о if letсвязывании (во многих случаях это устраняет целый слой вложенности). Там нет ничего о необязательной цепочки.
nhgrif

6
И когда вы упоминаете неявно развернутые необязательные опции, вы даже не упоминаете, что можете использовать все вышеупомянутые необязательные приемы, чтобы безопасно развернуть неявно развернутые необязательные опции. Мы также не рассматриваем развертывание нескольких вариантов в одном и том же предложении.
nhgrif

1
@nhgrif, я сказал: «Вы также можете использовать try / catch или обработку ошибок guard, но обычно один из других методов выше - чище». У вас наверняка есть хорошие моменты. Это довольно большая тема. Как насчет того, чтобы добавить свой собственный ответ, который охватывает вещи, которые я не сделал, вместо того, чтобы комментировать? Таким образом, вопрос и его ответы становятся лучшим преимуществом для сайта.
Дункан C

13

Поскольку приведенные выше ответы четко объясняют, как безопасно играть с Optionals. Я постараюсь объяснить, что на самом деле опционально быстро.

Еще один способ объявить необязательную переменную

var i : Optional<Int>

А необязательный тип - это не что иное, как перечисление с двумя падежами, т.е.

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

Таким образом, чтобы присвоить ноль нашей переменной «я». Мы можем сделать var i = Optional<Int>.none или присвоить значение, мы передадим некоторое значение var i = Optional<Int>.some(28)

Согласно Свифту, «ноль» - это отсутствие стоимости. И для создания экземпляра, инициализированного с помощью. nilМы должны соответствовать протоколу, который называется ExpressibleByNilLiteralи отлично, если вы догадались, только Optionalsсоответствовать ExpressibleByNilLiteralи соответствовать другим типам не рекомендуется.

ExpressibleByNilLiteralимеет единственный вызванный метод, init(nilLiteral:)который инициализирует instace с nil. Вы обычно не вызываете этот метод, и в соответствии с быстрой документацией не рекомендуется вызывать этот инициализатор напрямую, так как компилятор вызывает его всякий раз, когда вы инициализируете необязательный тип nilлитералом.

Даже я должен обернуть (не каламбур) голову вокруг Факультативного: D Happy Swfting All .


12

Во-первых, вы должны знать, что такое необязательное значение. Вы можете перейти на язык программирования Swift для деталей.

Во-вторых, вы должны знать, что необязательное значение имеет два статуса. Один - полное значение, а другой - нулевое значение. Поэтому, прежде чем реализовать необязательное значение, вы должны проверить, в каком оно состоянии.

Вы можете использовать if let ...или guard let ... elseи так далее.

Еще один способ, если вы не хотите проверять состояние переменной перед реализацией, вы также можете использовать var buildingName = buildingName ?? "buildingName"вместо этого.


8

Однажды у меня была эта ошибка, когда я пытался установить свои значения Outlets из метода подготовки к переходу следующим образом:

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
}

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


Только не забудьте упомянуть, куда звонить updateViewв контроллере назначения;)
Ахмад Ф

@ Ахмад, хорошая мысль. Тем не менее, вызов updateViewконтроллера in в этом случае не требуется, так как я использую nameпеременную для установки nameLabel.textв viewDidLoad. Но если бы мы выполняли большую настройку, то, безусловно, было бы лучше создать другую функцию, сделав это и вызвав ее viewDidLoadвместо этого.
Wissa

7

По сути, вы пытались использовать значение nil в тех местах, где Swift допускает только значения, отличные от nil, сказав, что компилятор доверяет вам, что там никогда не будет значения nil, что позволяет вашему приложению компилироваться.

Есть несколько сценариев, которые приводят к такой фатальной ошибке:

  1. принудительное развертывание:

    let user = someVariable!

    Если someVariableноль, то вы получите сбой. Делая принудительную развёртку, вы перекладывали ответственность за проверку на ноль от компилятора к вам, в основном, выполняя принудительную развёртку, вы гарантируете компилятору, что там никогда не будет нулевых значений. И угадайте, что произойдет, если каким-то образом значение nil закончится someVariable?

    Решение? Используйте необязательное связывание (иначе if-let), выполните обработку переменной там:

    if user = someVariable {
        // do your stuff
    }
  2. принудительное (вниз) приведение:

    let myRectangle = someShape as! Rectangle

    Здесь путем принудительного приведения вы говорите компилятору больше не беспокоиться, так как у вас всегда будет Rectangleэкземпляр. И пока это действительно так, вам не о чем беспокоиться. Проблемы начинаются, когда вы или ваши коллеги из проекта начинаете распространять не прямоугольные значения.

    Решение? Используйте необязательное связывание (иначе if-let), выполните обработку переменной там:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. Неявно развернутые опции. Предположим, у вас есть следующее определение класса:

    class User {
        var name: String!
    
        init() {
            name = "(unnamed)"
        }
    
        func nicerName() {
            return "Mr/Ms " + name
        }
    }

    Теперь, если никто не запутался со nameсвойством, установив его в значение nil, то оно работает как положено, однако, если Userинициализируется из JSON, в котором отсутствует nameключ, вы получите фатальную ошибку при попытке использовать свойство.

    Решение? Не используйте их :) Если вы не уверены на 102%, что свойство всегда будет иметь значение, отличное от нуля, к моменту его использования. В большинстве случаев преобразование в необязательный или необязательный будет работать. Если вы сделаете его необязательным, компилятор также поможет вам, указав пропущенные вами пути кода, указав значение этого свойства.

  4. Не подключенные или еще не подключенные розетки. Это частный случай сценария № 3. По сути, у вас есть некоторый загруженный XIB класс, который вы хотите использовать.

    class SignInViewController: UIViewController {
    
        @IBOutlet var emailTextField: UITextField!
    }

    Теперь, если вы пропустили подключение к розетке из редактора XIB, приложение завершит работу, как только вы захотите использовать розетку. Решение? Убедитесь, что все розетки подключены. Или используйте ?оператор на них: emailTextField?.text = "my@email.com". Или объявите выход как опциональный, хотя в этом случае компилятор заставит вас развернуть его по всему коду.

  5. Значения, поступающие из Objective-C, и которые не имеют аннотаций обнуляемости. Предположим, у нас есть следующий класс Objective-C:

    @interface MyUser: NSObject
    @property NSString *name;
    @end

    Теперь, если аннотации обнуляемости не указаны (явно или через NS_ASSUME_NONNULL_BEGIN/ NS_ASSUME_NONNULL_END), тогда nameсвойство будет импортировано в Swift как String!(IUO - неявно развернутый необязательно). Как только какой-то swift-код захочет использовать это значение, он потерпит неудачу, если nameравен нулю.

    Решение? Добавьте аннулируемые аннотации к вашему коду Objective-C. Однако остерегайтесь, компилятор Objective-C немного допустим, когда дело доходит до обнуляемости, вы можете получить нулевые значения, даже если вы явно пометили их как nonnull.


2

Это более важный комментарий, и поэтому неявно развернутые дополнительные параметры могут быть обманчивы, когда речь идет об отладке 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
}

2

Ошибки EXC_BAD_INSTRUCTIONи fatal error: unexpectedly found nil while implicitly unwrapping an Optional valueпоявляются чаще всего, когда вы объявили @IBOutlet, но не подключены к раскадровке .

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


не должна ли @IBOutletпричина этой ошибки иметь фатальную ошибку: неожиданно найден ноль при неявном развертывании версии ошибки с дополнительным значением ?
Пкамб

1
Это правда. Возможно, когда я отправляю ответ, это то, что я имел в виду, и я копирую вставленное первое фатальное сообщение об ошибке. Ответ от Хэмиша выглядит очень полным об этом.
Эль Мохамад

2

Если вы получаете эту ошибку в CollectionView, попробуйте создать файл CustomCell и Custom xib.

добавить этот код в ViewDidLoad () на mainVC.

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")

0

Я столкнулся с этой ошибкой при переходе от контроллера табличного представления к контроллеру представления, потому что я забыл указать имя пользовательского класса для контроллера представления в основной раскадровке.

Что-то простое, что стоит проверить, если все остальное выглядит хорошо

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.