Що означає "Фатальна помилка: несподівано знайдений нуль під час розгортання необов'язкового значення"?


415

У моїй програмі Swift відбувається збій EXC_BAD_INSTRUCTIONі одна з наступних подібних помилок. Що означає ця помилка та як її виправити?

Фатальна помилка: Несподівано знайдено нуль під час розгортання необов'язкового значення

або

Фатальна помилка: Несподівано знайдений нуль під час неявного розгортання необов'язкового значення


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


8
@RobertColumbia, це пара спільнот з питаннями Wiki & Q з великою документацією щодо цього питання, я не думаю, що це питання не повинно бути закритим через відсутність MCVE.
JAL

Створіть подібну змінну [var nameOfDaughter: String? ] і використовувати таку змінну, як ця [якщо нехай _ = nameOfDaughter {}] - найкращий підхід.
iOS

Відповіді:


673

Ця відповідь - вікі спільноти . Якщо ви вважаєте, що це можна зробити краще, сміливо редагуйте це !

Передумови: Що необов’язково?

У Swift Optional- це загальний тип, який може містити значення (будь-якого виду) або взагалі не мати значення.

У багатьох інших мовах програмування певне значення "дозорного" часто використовується для вказівки на відсутність значення . Наприклад, у Objective-C nil( нульовий вказівник ) вказує на відсутність об'єкта. Але це стає більш складним при роботі з примітивними типами - його слід -1використовувати для позначення відсутності цілого чи, можливо INT_MIN, або якогось іншого цілого числа? Якщо якесь конкретне значення обрано для значення "немає цілого числа", це означає, що воно більше не може розглядатися як дійсне значення.

Swift - безпечна для типу мова, це означає, що мова допомагає зрозуміти типи значень, з якими може працювати ваш код. Якщо частина вашого коду очікує String, введіть безпеку, яка не дозволяє вам помилково передати його 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 необов'язкові обмеження не обмежуються об'єктами / типовими типами. Необов'язково поводиться аналогічно Haskell's Maybe .)


Чому я отримав " фатальну помилку: несподівано знайдений нуль під час розгортання необов'язкового значення "?

Щоб отримати доступ до необов'язкового значення (якщо воно є у нього взагалі), потрібно його розгорнути . Необов’язкове значення можна розкрутити безпечно або насильно. Якщо примусово розгортати необов’язковий, і він не мав значення, програма перестане працювати з наведеним вище повідомленням.

Xcode покаже вам збій, виділивши рядок коду. Проблема виникає на цій лінії.

розбита лінія

Цей збій може статися при двох різних видах відкручування сили:

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

Це робиться з !оператором за бажанням. Наприклад:

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

Фатальна помилка: Несподівано знайдено нуль під час розгортання необов'язкового значення

Як anOptionalStringі nilтут, ви отримаєте аварію на лінії, де ви змусите її відкрутити.

2. Неявно розгорнута опція

Вони визначаються з типом a !, а не ?після.

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

Передбачається, що ці необов'язкові елементи містять значення. Тому щоразу, коли ви отримуєте доступ до неявно розгорнутого факультативу, він автоматично буде розкручений для вас. Якщо воно не містить значення, воно вийде з ладу.

print(optionalDouble) // <- CRASH

Фатальна помилка: Несподівано знайдений нуль під час неявного розгортання необов'язкового значення

Для того, щоб визначити, яка змінна спричинила збій, ви можете утримувати , натискаючи, щоб показати визначення, де ви можете знайти необов'язковий тип.

Зокрема, IBOutlets, як правило, неявно розгортається. Це відбувається тому, що ваш xib або аркуш розказок зв’язують торгові точки під час виконання, після ініціалізації. Тому ви повинні переконатися, що ви не отримуєте доступ до торгових точок, перш ніж вони завантажуються. Ви також повинні перевірити правильність з'єднань у вашому файлі розкадрівки / xib, інакше значення будуть nilпід час виконання, і тому вони завершаться, коли вони неявно розгорнуті. . Виправляючи з'єднання, спробуйте видалити рядки коду, які визначають ваші торгові точки, а потім знову підключіть їх.


Коли я коли-небудь повинен змусити розгортати необов’язково?

Явна сила розгортання

Як правило, ви ніколи не повинні прямо змушувати розгортати необов'язкові !оператори. Можливо, є випадки, коли використання !є прийнятним, але ви повинні будь-коли використовувати його, лише якщо ви на 100% впевнені, що необов'язково містить значення.

У той час як може бути випадок , коли ви можете використовувати силу розгортати, як ви знаєте , на самому ділі , що факультативний містить значення - немає єдиного місця , де ви не можете сміливо розгортати , що необов'язково замість цього.

Неявно розгорнута опція

Ці змінні розроблені так, що ви можете відкласти їх призначення на пізніше у своєму коді. Це ваша відповідальність , щоб переконатися , що вони мають значення , перш ніж отримати доступ до них. Однак, оскільки вони передбачають розгортання сили, вони все ще є по своїй суті небезпечними - оскільки вони вважають, що ваше значення не відповідає нулю, навіть якщо призначення нуля є дійсним.

Ви повинні використовувати лише неявно розгорнуті необов'язкові додатки в крайньому випадку . Якщо ви можете використовувати змінну змінну або вказати значення за замовчуванням для змінної - вам слід зробити це замість того, щоб використовувати необов’язково розгорнуту необов’язковість.

Однак є кілька сценаріїв, коли неявно розгорнуті опціони є корисними , і ви все ще можете використовувати різні способи безпечного їх розгортання, як зазначено нижче, - але ви завжди повинні використовувати їх з належною обережністю.


Як я можу безпечно боротися з необов’язковими?

Найпростіший спосіб перевірити, чи містить необов'язкове значення, - це порівняти його nil.

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

Однак 99,9% часу, працюючи з необов'язковими, ви дійсно хочете отримати доступ до значення, яке воно містить, якщо воно взагалі містить. Для цього можна скористатись додатковою палітуркою .

Необов’язкове палітурка

Необов’язкове прив'язування дозволяє перевірити, чи містить необов'язкове значення - і дозволяє призначити розпаковане значення новій змінній або константі. Він використовує синтаксис 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")
}

Для цього спочатку перевірте, чи необов'язково містить значення. Якщо це так , то значення "unpapped" присвоюється новій змінній ( number) - якою ви можете вільно користуватися, немовби необов'язковою. Якщо необов'язковий не містить значення, тоді буде застосовано інше, як ви і очікували.

Що стосується необов'язкового прив’язки, ви можете розгортати кілька необов’язків одночасно. Можна просто відокремити твердження комою. Заява буде успішною, якщо всі необов'язкові параметри були розгорнуті.

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)!")

Охоронці також підтримують ті ж акуратні підказки, що й оператор if, який підтримується, наприклад, розгортання кількох необов’язків одночасно та використання whereпункту.

Чи використовувати ви оператор if або guard повністю, залежить від того, чи потребує будь-який майбутній код необов'язковий вміст значення.

Nil Coalescing Operator

Оператор Nil Coalescing - це чудова скорочена версія потрійного умовного оператора , призначена в основному для перетворення необов'язкових в необов'язкові. Він має синтаксис a ?? b, де aє необов'язковим типом і bє тим самим типом, що і a(хоча зазвичай є необов'язковим).

Це по суті дозволяє сказати "Якщо aмістить значення, розгорніть його. Якщо він не повернеться, bзамість цього ». Наприклад, ви можете використовувати його так:

let number = anOptionalInt ?? 0

Це визначатиме numberконстанту Intтипу, яка або буде містити значення anOptionalInt, якщо воно містить значення, або 0іншим чином.

Це просто скорочення:

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

Необов’язковий ланцюжок

Ви можете скористатись опціональним ланцюжком , щоб викликати метод або отримати доступ до властивості за бажанням. Це просто робиться за допомогою суфіксації назви змінної з а ?при її використанні.

Наприклад, скажімо, у нас є змінна 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, ми можемо використовувати дозволити 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
Особисто я хотів би подякувати вам за те, що ви доклали зусиль для написання всього цього. Я думаю, що це, безумовно, буде корисним як для початківців, так і для експертів, використовуючи швидку.
Pranav Wadhwa

15
Я радий, що ти вважаєш це корисним. Ця відповідь - вікі спільноти, тому це співпраця між багатьма людьми (7, поки що)!
jtbandes

1
Ця помилка в моєму випадку переривчаста. Будь-які пропозиції? Код в stackoverflow.com/questions/50933681 / ...
py_ios_dev

1
Може бути , настав час оновити цей відповідь , щоб використовувати compactMap()замість flatMap().
Nicolas Miari

тож я здогадуюсь найкращим і коротким рішенням є "Nil Coalescing Operator", правда?
mehdigriche

65

TL; DR відповідь

За дуже невеликими винятками , це правило є золотим:

Уникайте використання !

Оголосити змінну необов’язковою ( ?), а не неявно розгорнутою необов’язкою (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

Це питання виникає ВСЕ ЧАС НА ТО. Це одне з перших речей, з якими стикаються нові розробники Swift.

Фон:

Swift використовує поняття "необов'язково" для роботи зі значеннями, які можуть містити значення чи ні. В інших мовах, таких як C, ви можете зберігати значення 0 у змінній, щоб вказати, що воно не містить значення. Однак що робити, якщо 0 - дійсне значення? Тоді ви можете використовувати -1. Що робити, якщо -1 - дійсне значення? І так далі.

Додаткові опції Swift дозволяють вам налаштувати змінну будь-якого типу, яка міститиме дійсне значення, або не має значення.

Ви ставите знак питання після типу, коли ви оголошуєте змінну на середню (тип x, або немає значення).

Необов'язковий - це фактично контейнер, ніж містить змінну заданого типу, або нічого.

Факультативно потрібно "розгортати", щоб отримати значення всередині.

Значок "!" Оператор - оператор "розгортання сили". Він говорить "довіряй мені. Я знаю, що роблю. Я гарантую, що коли цей код працює, змінна не буде містити нуля". Якщо ви помиляєтесь, ви зриваєтесь.

Якщо ви дійсно не знаєте, що робите, уникайте "!" примусовий оператор розкручування. Це, мабуть, найбільше джерело збоїв для початківців програмістів Swift.

Як поводитися з необов’язком:

Існує маса інших способів поводження з необов'язковими, які є більш безпечними. Ось деякі (не вичерпний список)

Ви можете використовувати "необов'язкове прив'язування" або "якщо дозволити" сказати ", якщо цей необов'язковий містить значення, збережіть це значення у новій необов'язковій змінній. Якщо необов'язковий не містить значення, пропустіть тіло цього параметра, якщо заява ".

Ось приклад необов'язкового зв’язування з нашим fooнеобов'язковим:

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
}

Записи Guard були додані в Swift 2. Guard дозволяє зберегти "золотий шлях" через ваш код і уникати постійно зростаючих рівнів вкладених ifs, які іноді є результатом використання необов'язкового прив'язки "if let".

Існує також конструкція, яка називається "оператором злиття нуля". Він приймає форму "необов'язково_вар ?? заміна_валь". Він повертає необов'язкову змінну з тим самим типом, що і дані, що містяться в необов'язковій. Якщо необов'язковий містить нуль, він повертає значення виразу після "??" символ.

Отже, ви можете використовувати такий код:

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

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

Редагувати:

Інший, трохи більш тонкий gotcha з необов’язком - це "неявно розгорнута необов'язковість. Коли ми оголосимо foo, ми могли б сказати:

var foo: String!

У цьому випадку foo як і раніше є необов’язковим, але вам не потрібно розгортати його, щоб посилатися на нього. Це означає, що кожного разу, коли ви намагаєтеся посилатись на foo, ви руйнуєтесь, якщо це нуль.

Отже цей код:

var foo: String!


let upperFoo = foo.capitalizedString

Збій посилань на властивість foo capitalizedString, навіть якщо ми не розгортаємо foo. друк виглядає чудово, але це не так.

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

Підсумок: Коли ви вперше вивчаєте Свіфт, зробіть вигляд "!" персонаж не є частиною мови. Це, ймовірно, може ввести вас у біду.


7
Вам слід подумати про те, щоб зробити це публічним вікі.
vacawama

3
Відредагуйте свою відповідь, а під полем редагування праворуч встановлено прапорці вікі спільноти . Ви більше не отримаєте представника для відповіді, але я знаю, що це все одно не кричущий реп.
vacawama

6
Зважаючи на те, що це питання задають мільйон разів на сайті, якби я збирався витратити час, щоб поставити запитання з самовідповіддю як канонічну відповідь на питання, я би сподівався, що відповідь набагато повніша, ніж ця. Про guardпункти немає нічого . Немає нічого про використання, if varяке є настільки ж дійсним як конструкція if let. Нічого про whereпункти, які, на мою думку, не варто згадувати, коли ми говоримо про if letприв'язку (це видаляє цілий шар гніздування в багатьох випадках). Немає нічого про додаткове прикування.
nhgrif

6
І коли ви згадуєте неявно розгорнуті необов'язкові елементи, ви навіть не згадуєте, що можете використовувати всі вищезгадані факультативні прийоми, щоб безпечно розгортати неявно розгорнуті необов’язкові додатки. Ми також не охоплюємо розгортання кількох варіантів в одному пункті.
nhgrif

1
@nhgrif, я сказав: "Ви також можете використовувати" try / catch "чи обробку помилок, але одна з інших методик вище є чистішою". У вас, безумовно, є кілька хороших моментів. Це досить велика тема. Як щодо того, щоб надати власну відповідь, яка стосується речей, які я не зробив, а не робити снайперські коментарі? Таким чином питання та його відповіді стають кращим надбанням сайту.
Дункан C

13

Оскільки вищевказані відповіді чітко пояснюють, як безпечно грати з Факультативом. Я спробую пояснити, що необов’язково насправді швидко.

Ще один спосіб оголошення необов’язкової змінної - це

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літералом.

Навіть мені самому доводиться обертати (не каламбур) голову навколо Необов’язково: D Happy Swfting All .


12

По-перше, ви повинні знати, що таке необов’язкове значення. Ви можете перейти до мови швидкої програми Swift для детальної інформації.

По-друге, ви повинні знати, що необов'язкове значення має два статуси. Одне - це повне значення, а інше - нульове значення. Отже, перш ніж реалізувати необов’язкове значення, слід перевірити, у якому стані воно знаходиться.

Можна використовувати if let ... або guard let ... elseтощо.

Ще один спосіб, якщо ви не хочете перевіряти стан змінної перед реалізацією, ви також можете використовувати var buildingName = buildingName ?? "buildingName"замість цього.


8

У мене була помилка одного разу, коли я намагався встановити свої значення Outlets з методу підготовки до segue наступним чином:

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в контролер призначення;)
Ахмад F

@AhmadF хороший момент. Але updateViewв цьому випадку виклик в контролері призначення не потрібен, оскільки я використовую nameзмінну для встановлення nameLabel.textв viewDidLoad. Але якби ми велику кількість налаштувань, то, безумовно, було б краще створити іншу функцію, зробивши це, і викликати її viewDidLoadзамість цього.
Вісса

7

В основному ви намагалися використовувати значення нуля в місцях, де Swift дозволяє лише ненульові значення, сказавши компілятору довіряти вам, що там ніколи не буде нульового значення, тим самим дозволяючи вашому додатку збиратись.

Існує кілька сценаріїв, які призводять до такого роду фатальної помилки:

  1. примусові розгортання:

    let user = someVariable!

    Якщо someVariable нуль, то ви отримаєте крах. Роблячи примусовий розмотування, ви перенесли відповідальність з нульової перевірки з компілятора на вас, в основному, роблячи примусове розкручування, ви гарантуєте компілятору, що ви ніколи не матимете нульових значень там. І здогадайтесь, що відбувається, якщо якось нульове значення закінчується вsomeVariable ?

    Рішення? Використовуйте необов'язкове прив'язування (aka if-let), виконайте обробку змінної там:

    if user = someVariable {
        // do your stuff
    }
  2. вимушені (вниз) ролі:

    let myRectangle = someShape as! Rectangle

    Тут примусовим кастингом ви скажете компілятору більше не хвилюватися, оскільки ви завжди матимете Rectangle примірник. І поки це має місце, вам не доведеться турбуватися. Проблеми починаються, коли ви або ваші колеги з проекту починаєте циркулювати значення не прямокутника.

    Рішення? Використовуйте необов'язкове прив'язування (aka 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 - неявно розпаковане необов'язково). Як тільки якийсь швидкий код захоче використовувати це значення, воно вийде з ладу, якщо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причина цієї помилки мати фатальну помилку: Несподівано знайдено нуль, при цьому неявно розгортаючи необов’язкове значення помилки?
pkamb

1
Це правда. Можливо, коли я надсилаю відповідь, це те, що я мав на увазі, і я копіюю вклеєне перше повідомлення про фатальну помилку. Відповідь Гаміша щодо цього виглядає дуже повно.
Ale Mohamad

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.