Ця відповідь - вікі спільноти . Якщо ви вважаєте, що це можна зробити краще, сміливо редагуйте це !
Передумови: Що необов’язково?
У 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?
- у рідкісних випадках, коли помилка не важлива.
Ресурси