Необов’язковий варіант Swift - це тип, який може містити або значення, або не має значення. Необов’язкові записуються шляхом додавання ?
до будь-якого типу:
var name: String? = "Bertie"
Необов'язкові параметри (поряд із Generics) - одна з найскладніших концепцій Свіфта. Через те, як вони написані та використовуються, легко зрозуміти неправильне уявлення про те, що вони є. Порівняйте додатковий варіант із створенням звичайної рядки:
var name: String = "Bertie" // No "?" after String
З синтаксису схоже, що необов'язковий рядок дуже схожий на звичайний рядок. Це не. Необов’язковий рядок - це не рядок із увімкненою "необов'язковою" настройкою. Це не особлива різноманітність String. Рядок і необов'язковий рядок - це абсолютно різні типи.
Ось найважливіше, що потрібно знати: Необов’язково - це тип контейнера. Додатковий рядок - це контейнер, який може містити рядок. Необов'язковий Int - контейнер, який може містити Int. Подумайте про необов'язковий як вид посилки. Перш ніж відкрити його (або "розмотати" мовою за бажанням), ви не знатимете, чи містить він щось чи нічого.
Ви можете побачити, як додаткові елементи реалізовані в стандартній бібліотеці Swift, ввівши "Необов’язково" у будь-який файл Swift і ⌘ клацнувши по ньому. Ось важлива частина визначення:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Необов’язково - це лише enum
один із двох випадків: .none
або .some
. Якщо це.some
, є пов’язане значення, яке у прикладі вище буде String
"Привіт". Необов'язково використовує Generics, щоб надати тип пов’язаному значенню. Тип необов'язкового рядка не String
є Optional
, або точніше Optional<String>
.
Все, що Swift робить з додатковими можливостями, це магія, щоб зробити читання та запис коду більш вільним. На жаль, це затьмарює те, як воно насправді працює. Пізніше я перегляну кілька хитрощів.
Примітка: Я буду говорити про необов'язкові змінні дуже багато, але добре також створювати необов'язкові константи. Я позначаю всі змінні за їх типом, щоб полегшити розуміння типів створюваних типів, але вам не доведеться робити це у власному коді.
Як створити необов'язкові
Щоб створити необов'язковий, додайте а ?
після типу, який потрібно обернути. Будь-який тип може бути необов’язковим, навіть ваші власні типи. Ви не можете мати пробіл між типом і ?
.
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
Використання додаткових варіантів
Ви можете порівняти необов'язковий, nil
щоб побачити, чи має він значення:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
Це трохи заплутано. Це означає, що необов'язково - це або інше, або інше. Це або нуль, або це "Боб". Це неправда, необов'язковий не перетворюється на щось інше. Порівнювати його з нулем - трюк, щоб зробити простіший для читання код. Якщо необов'язково дорівнює нулю, це просто означає, що перерахунок наразі встановлений .none
.
Тільки необов'язкові можуть бути нульовими
Якщо ви спробуєте встановити необов'язкову змінну до нуля, ви отримаєте помилку.
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
Інший спосіб розгляду додаткових опцій - це доповнення до звичайних змінних Swift. Вони є аналогом змінної, яка гарантовано має значення. Свіфт - це дбайлива мова, яка ненавидить неоднозначність. Більшість змінних визначаються як необов'язкові, але іноді це неможливо. Наприклад, уявіть контролер перегляду, який завантажує зображення або з кеша, або з мережі. Це зображення може мати або не мати його під час створення контролера перегляду. Немає способу гарантувати значення змінної зображення. У цьому випадку вам доведеться зробити це необов’язковим. Він починається якnil
і коли витягуєте зображення, необов'язково отримує значення.
Використання факультативного виявляє наміри програмістів. Порівняно з Objective-C, де будь-який об’єкт може бути нульовим, Swift потребує того, щоб вам було зрозуміло, коли значення може бути відсутнім і коли воно гарантовано існує.
Щоб використовувати необов'язковий, ви "розгортаєте" його
Факультативний String
не може бути використаний замість фактичного String
. Щоб використовувати загорнуте значення всередині необов’язкового, його потрібно розгортати. Найпростіший спосіб розгортати необов’язковий варіант - це додавання !
після необов'язкового імені. Це називається "розгортання сили". Він повертає значення всередині необов'язкового (як оригінальний тип), але якщо необов'язковий nil
, він спричиняє збій часу виконання. Перш ніж розгортати, ви повинні переконатися, що є значення.
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
Перевірка та використання необов’язкового
Оскільки ви завжди повинні перевіряти наявність нуля перед тим, як розгортати та використовувати необов'язковий, це звичайний зразок:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
У цій шаблоні ви перевіряєте, чи є якесь значення, а тоді, коли ви впевнені, що воно є, ви змушуєте розгортати його у тимчасову константу для використання. Оскільки це звичайна річ, Swift пропонує ярлик, використовуючи "якщо дозволити". Це називається "необов'язкове зв'язування".
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Це створює тимчасову константу (або змінну , якщо замінити let
з var
), областю дії якого тільки в межах , якщо це брекети - х. Оскільки використання імені типу "unrappedMealPreference" або "realMealPreference" є тягарем, Swift дозволяє повторно використовувати початкове ім'я змінної, створюючи тимчасове в межах дужки
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
Ось код, який демонструє, що використовується інша змінна:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
Необов'язкове прив'язування працює, перевіряючи, чи необов'язковий дорівнює нулю. Якщо цього не відбувається, він розгортає необов'язкове в задану константу і виконує блок. У Xcode 8.3 та пізніших версіях (Swift 3.1) спроба надрукувати подібну необов’язковість спричинить марне попередження. Використовуйте необов’язкові, debugDescription
щоб заглушити це:
print("\(mealPreference.debugDescription)")
Для чого необов’язкові?
Необов’язково два випадки використання:
- Те, що може вийти з ладу (я чогось очікував, але нічого не отримав)
- Речі, які зараз є нічим, але можуть бути чимось пізніше (і навпаки)
Деякі конкретні приклади:
- Властивість, яка може бути там чи ні, як у класі , так
middleName
і spouse
в нійPerson
- Метод, який може повернути значення або нічого, наприклад пошук відповідності в масиві
- Метод, який може повернути результат або отримати помилку і нічого не повернути, як, наприклад, спроба прочитати вміст файлу (який зазвичай повертає дані файлу), але файл не існує
- Властивості делегування, які не завжди потрібно встановлювати і зазвичай встановлюються після ініціалізації
- Для
weak
властивостей у класах. Те, на що вони вказують, може налаштуватись nil
у будь-який час
- Великий ресурс, який, можливо, доведеться випустити для відновлення пам'яті
- Коли вам потрібен спосіб дізнатися, коли встановлено значення (дані ще не завантажені> дані), а не використовувати окремі завантажені дані
Boolean
Необов’язковість не існує в Objective-C, але існує рівнозначна концепція, яка повертає нуль. Методи, які можуть повернути об'єкт, можуть замість цього повернути нуль. Це прийнято означати "відсутність дійсного об'єкта" і часто використовується для того, щоб сказати, що щось пішло не так. Він працює лише з об'єктами Objective-C, а не з примітивами або основними типами С (перерахунки, структури). Objective-C часто спеціалізувався типами являє відсутність цих значень ( NSNotFound
яка насправді NSIntegerMax
, kCLLocationCoordinate2DInvalid
являє неприпустимі координати, -1
або яке - або негативне значення також використовується). Кодер повинен знати про ці особливі значення, тому вони повинні бути задокументовані та вивчені для кожного випадку. Якщо метод не може взятись nil
за параметр, це необхідно задокументувати. В Objective-C,nil
був покажчиком так само, як усі об'єкти визначалися як покажчики, але nil
вказував на конкретну (нульову) адресу. У Swift nil
- буквальне, що означає відсутність певного типу.
Порівняння з nil
Ви раніше могли використовувати будь-яку необов'язкову як Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
В останніх версіях Swift ви повинні використовувати leatherTrim != nil
. Чому це? Проблема полягає в тому, що балончик Boolean
може бути загорнутий у необов'язковий. Якщо у вас є Boolean
такий:
var ambiguous: Boolean? = false
він має два види "хибних", той, де немає значення, і той, де він має значення, але значення є false
. Свіфт ненавидить неоднозначність, тому тепер завжди потрібно перевіряти необов’язковість проти nil
.
Вам може бути цікаво, в чому Boolean
полягає необов’язковість ? Як і в інших опціонах, .none
стан може вказувати на те, що значення поки невідоме. На іншому кінці мережевого дзвінка може бути щось, що потребує певного часу для опитування. Необов’язкові булеви також називають " трицінні булеви "
Швидкі хитрощі
Swift використовує деякі хитрощі, щоб дозволити роботі необов'язкових. Розглянемо ці три рядки звичайного необов’язкового коду;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Жоден із цих рядків не повинен складатися.
- Перший рядок встановлює необов'язковий рядок, використовуючи лінійку String, два різних типи. Навіть якщо це був
String
тип, то різні
- Другий рядок встановлює необов'язковий два рядки "String to nil"
- Третій рядок порівнює необов'язковий рядок з нулем, два різні типи
Я ознайомлюсь із деякими деталями реалізації додаткових опцій, які дозволяють цим рядкам працювати.
Створення необов'язкового
Використовувати ?
для створення необов'язкового є синтаксичний цукор, включений компілятором Swift. Якщо ви хочете зробити це довгим шляхом, ви можете створити необов'язковий такий:
var name: Optional<String> = Optional("Bob")
Це викликає Optional
перший ініціалізатор, public init(_ some: Wrapped)
який визначає асоційований тип необов'язкового типу, який використовується в дужках.
Ще довший спосіб створення та налаштування необов’язкового:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
Налаштування необов'язкового для nil
Ви можете створити необов'язкове без початкового значення або створити початкове значення nil
(обидва мають однаковий результат).
var name: String?
var name: String? = nil
Дозволення необов'язкових рівних nil
увімкнено протоколом ExpressibleByNilLiteral
(раніше названим NilLiteralConvertible
). Опціональний створюються з Optional
другим ініціалізатор «S, public init(nilLiteral: ())
. Документи говорять, що не слід використовувати ExpressibleByNilLiteral
нічого, крім додаткового, оскільки це змінило б значення нуля у вашому коді, але це можливо:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
Цей же протокол дозволяє встановити вже створений необов'язковий nil
. Хоча це не рекомендується, ви можете використовувати ініціалізатор nil literal безпосередньо:
var name: Optional<String> = Optional(nilLiteral: ())
Порівняння необов’язкового до nil
Додаткові параметри визначають двох спеціальних операторів "==" та "! =", Які ви можете побачити у Optional
визначенні. Перший ==
дозволяє перевірити, чи будь-який необов'язковий дорівнює нулю. Дві різні необов'язкові параметри, встановлені на .none, завжди будуть рівними, якщо асоційовані типи однакові. Коли ви порівнюєте з нулем, за лаштунками Swift створює необов'язковий аналогічний тип, встановлений на .none, а потім використовує для порівняння.
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
Другий ==
оператор дозволяє порівняти дві необов’язкові. Обидва повинні бути одного типу, і цьому типу потрібно відповідати Equatable
(протокол, який дозволяє порівнювати речі з звичайним оператором "=="). Швидкий (імовірно) розгортає два значення і безпосередньо порівнює їх. Він також обробляє випадок, коли один або обидва необов'язкові .none
. Зверніть увагу на відмінність порівняння з nil
буквальним.
Крім того, це дозволяє порівняти будь-який Equatable
тип із необов'язковим упаковкою цього типу:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
За лаштунками Swift обгортає необов'язкове як необов'язкове перед порівнянням. Він також працює з літералами ( if 23 == numberFromString {
)
Я сказав, що є два ==
оператори, але насправді є третій, який дозволяє вам поставити nil
ліву частину порівняння
if nil == name { ... }
Називання необов’язкових
Не існує угоди Swift для назви необов'язкових типів, відмінних від необов'язкових типів. Люди уникають додати щось до імені, щоб показати, що це необов’язкове (наприклад, "optionalMiddleName" або "possibleNumberAsString"), і нехай декларація показує, що це необов'язковий тип. Це стає складним, коли ви хочете назвати щось, щоб утримувати значення від необов'язкового. Ім'я "middleName" означає, що це тип String, тому коли ви виймаєте з нього значення String, ви часто можете закінчуватись такими іменами, як "фактичнийMiddleName" або "unpappedMiddleName" або "realMiddleName". Використовуйте необов'язкове прив'язування та повторно використовуйте ім’я змінної, щоб обійти це.
Офіційне визначення
З розділу "Основи" мови швидкого програмування :
Swift також вводить необов'язкові типи, які обробляють відсутність значення. Необов’язкові кажуть або "є значення, і воно дорівнює х", або "значення взагалі не існує". Необов'язкові подібні до використання нуля з покажчиками в Objective-C, але вони працюють для будь-якого типу, а не лише для класів. Додаткові опції безпечніші та виразніші за нульові покажчики в Objective-C і лежать в основі багатьох найпотужніших функцій Свіфта.
Необов’язкові - приклад того, що Swift - це безпечна мова. Swift допомагає зрозуміти типи значень, з якими може працювати ваш код. Якщо частина вашого коду очікує String, введіть безпеку, яка не дозволяє вам помилково передати його Int. Це дає змогу фіксувати та виправляти помилки якомога раніше в процесі розробки.
На закінчення, ось вірш 1899 р. Про необов’язкові:
Вчора на сходах
я зустрів чоловіка, якого там
не було. Сьогодні його знову не було,
я б хотів, щоб він пішов
Антигоніш
Більше ресурсів: