Необязательным в Swift является тип, который может содержать либо значение, либо отсутствие значения. Необязательные написаны добавлением ?
к любому типу:
var name: String? = "Bertie"
Необязательные (вместе с Generics) являются одной из самых сложных концепций Swift для понимания. Из-за того, как они написаны и используются, легко понять, что они из себя представляют. Сравните необязательное выше с созданием обычной строки:
var name: String = "Bertie" // No "?" after String
Из синтаксиса это выглядит как необязательная строка очень похожа на обычную строку. Это не. Необязательная строка не является строкой с включенной «необязательной» настройкой. Это не особая разновидность String. Строка и необязательная строка - это совершенно разные типы.
Вот самая важная вещь, которую нужно знать: необязательный - это своего рода контейнер. Необязательная строка - это контейнер, который может содержать строку. Необязательный Int - это контейнер, который может содержать Int. Думайте о дополнительной как своего рода посылка. Прежде чем открыть его (или «развернуть» на языке опций), вы не узнаете, содержит ли он что-то или ничего.
Вы можете увидеть, как дополнительные функции реализованы в стандартной библиотеке Swift, введя «Optional» в любой файл Swift и нажав ⌘ по нему. Вот важная часть определения:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Необязательно, это просто enum
один из двух случаев: .none
или .some
. Если это .some
, есть связанное значение, которое в приведенном выше примере будет String
«Hello». Необязательный параметр использует 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")
}
Это немного сбивает с толку. Это подразумевает, что необязательным является либо одно, либо другое. Это либо ноль, либо «Боб». Это не правда, необязательное не превращается во что-то еще. Сравнение его с nil - это трюк, чтобы сделать код более простым для чтения. Если необязательный параметр равен нулю, это просто означает, что перечисление в настоящее время установлено на .none
.
Только опционально может быть ноль
Если вы попытаетесь установить для необязательной переменной значение nil, вы получите ошибку.
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.
Проверка и использование дополнительного
Поскольку вы всегда должны проверять 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
), областью действия которого только в пределах , если это брекеты - х. Поскольку использование имени, такого как «unwrappedMealPreference» или «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
в любое время
- Большой ресурс, который может быть освобожден для восстановления памяти
- Когда вам нужен способ узнать, когда установлено значение (данные еще не загружены> данные) вместо использования отдельного dataLoaded
Boolean
Необязательные в Objective-C не существуют, но существует эквивалентная концепция, возвращающая ноль. Методы, которые могут вернуть объект, могут вернуть nil. Это означает «отсутствие действительного объекта» и часто используется, чтобы сказать, что что-то пошло не так. Он работает только с объектами Objective-C, но не с примитивами или базовыми 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
. Почему это? Проблема в том, что a Boolean
может быть обернут в необязательный. Если у вас есть Boolean
такие:
var ambiguous: Boolean? = false
у него есть два типа «ложь», один, где нет значения, и другой, где он имеет значение, но значение равно false
. Свифт ненавидит двусмысленность, поэтому теперь вы всегда должны проверять опцию против nil
.
Вы можете спросить, в чем смысл необязательного Boolean
? Как и в случае других опций, .none
состояние может указывать, что значение пока неизвестно. На другом конце сетевого вызова может быть что-то, что требует некоторого времени для опроса. Необязательные логические значения также называются « трехзначными логическими значениями »
Быстрые трюки
Swift использует некоторые приемы, чтобы опциональные опции работали. Рассмотрим эти три строки обычного вида дополнительного кода;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Ни одна из этих строк не должна компилироваться.
- Первая строка устанавливает необязательную строку String с использованием литерала String двух разных типов. Даже если это были
String
разные типы
- Вторая строка устанавливает необязательную строку в ноль, два разных типа
- Третья строка сравнивает необязательную строку с нулем, два разных типа
Я рассмотрю некоторые детали реализации опций, которые позволяют этим линиям работать.
Создание необязательного
Использование ?
для создания необязательного синтаксического сахара, разрешено компилятором 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
вторым инициализатором public init(nilLiteral: ())
. Документы говорят, что вы не должны использовать ExpressibleByNilLiteral
ничего, кроме опциональных, так как это изменило бы значение nil в вашем коде, но это возможно сделать:
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:
var name: Optional<String> = Optional(nilLiteral: ())
Сравнивая необязательный к nil
Необязательные определяют два специальных оператора "==" и "! =", Которые вы можете увидеть в Optional
определении. Первый ==
позволяет вам проверить, равен ли любой необязательный параметр нулю. Два разных дополнительных параметра, которые установлены в .none, всегда будут равны, если связанные типы одинаковы. Когда вы сравниваете с nil, за кулисами 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
(протокол, который позволяет сравнивать вещи с помощью обычного оператора "=="). Swift (предположительно) разворачивает два значения и сравнивает их напрямую. Он также обрабатывает случай, когда один или оба из дополнительных опций .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 для именования необязательных типов, отличных от необязательных типов. Люди избегают добавлять что-либо к имени, чтобы показать, что это необязательный параметр (например, «AdditionalMiddleName» или «возможный номер_строки»), и позволяют объявлению показать, что это необязательный тип. Это становится трудным, когда вы хотите назвать что-то для хранения значения из необязательного. Название «middleName» подразумевает, что это тип String, поэтому, когда вы извлекаете из него значение String, вы часто можете получить такие имена, как «actualMiddleName» или «unwrappedMiddleName» или «realMiddleName». Используйте необязательное связывание и повторно используйте имя переменной, чтобы обойти это.
Официальное определение
Из «Основы» на языке программирования Swift :
Swift также вводит необязательные типы, которые обрабатывают отсутствие значения. Необязательные говорят, что «есть значение, и оно равно x» или «нет значения вообще». Необязательные функции аналогичны использованию nil с указателями в Objective-C, но они работают для любого типа, а не только для классов. Дополнительные функции более безопасны и более выразительны, чем нулевые указатели в Objective-C, и являются основой многих наиболее мощных функций Swift.
Необязательные являются примером того, что Swift является языком, безопасным для типов. Swift помогает вам понять типы значений, с которыми может работать ваш код. Если часть вашего кода ожидает строку, тип безопасности не позволяет вам передать ему Int по ошибке. Это позволяет вам выявлять и исправлять ошибки как можно раньше в процессе разработки.
Чтобы закончить, вот стихотворение 1899 года об опциях:
Вчера на лестнице
я встретил человека, которого там
не было. Сегодня его не было снова.
Мне бы хотелось, чтобы он ушел.
Антигонист
Больше ресурсов: