Ответ 1
Необязательный в Swift - тип, который может содержать значение или значение. Опционы записываются путем добавления ?
к любому типу:
var name: String? = "Bertie"
Опционы (вместе с Generics) - одна из самых сложных понятий Swift для понимания. Из-за того, как они написаны и используются, легко получить неправильное представление о том, что они собой представляют. Сравните опцию выше с созданием нормальной строки:
var name: String = "Bertie" // No "?" after String
Из синтаксиса выглядит, что необязательная String очень похожа на обычную String. Не это. Необязательная строка не является строкой с включенной "опциональной" настройкой. Это не особая разновидность String. Строка и необязательная строка являются совершенно разными типами.
Здесь самое главное знать: необязательный тип контейнера. Необязательная строка - это контейнер, который может содержать строку. Необязательный Int - это контейнер, который может содержать Int. Подумайте о необязательной форме своего рода посылки. Прежде чем открыть его (или "развернуть" на языке опций), вы не узнаете, содержит ли оно что-то или ничего.
Вы можете увидеть как реализованы дополнительные опции в стандартной библиотеке Swift, введя "Необязательно" в любой файл 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 - это трюк для упрощения чтения кода. Если необязательный равен nil, это означает, что в настоящее время перечисление установлено на .none
.
Только опции могут быть nil
Если вы попытаетесь установить необязательную переменную в 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.
Проверка и использование опционального
Поскольку вы всегда должны проверять нуль перед распаковкой и использовать опцию, это общий шаблон:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
В этом шаблоне вы проверяете, присутствует ли значение, а затем, когда вы уверены, что это так, вы принудительно разворачиваете его во временную константу. Поскольку это такая обычная вещь, Swift предлагает ярлык, используя "if let". Это называется "необязательным связыванием".
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Это создает временную константу (или переменную, если вы замените let
на var
), область видимости которой находится только в фигурных скобках if. Поскольку использование имени типа "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, но существует эквивалентная концепция, возвращающая нуль. Методы, которые могут возвращать объект, могут возвращать нуль. Это означает "отсутствие действительного объекта" и часто используется, чтобы сказать, что что-то пошло не так. Он работает только с объектами 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
. Swift ненавидит двусмысленность, поэтому теперь вы всегда должны проверить опцию nil
.
Вы можете задаться вопросом, что точка необязательного Boolean
? Как и в случае с другими опциями, состояние .none
может указывать на то, что значение пока неизвестно. На другом конце сетевого вызова может быть что-то, что требует времени для опроса. Необязательные булевы также называются трехзначные булевы"
Быстрые трюки
Swift использует некоторые трюки, чтобы позволить опциональным функциям работать. Рассмотрим эти три строки обычного перспективного кода;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Ни одна из этих строк не должна компилироваться.
- Первая строка устанавливает необязательную строку с использованием строкового литерала, двух разных типов. Даже если это было
String
, разные типы - Вторая строка устанавливает необязательную строку в nil, два разных типа
- Третья строка сравнивает необязательную строку с 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
второго инициализатора 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 literal напрямую:
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 a match!") // Prints "It a match!"
}
За кулисами Swift обертывает необязательный параметр как необязательный перед сравнением. Он также работает с литералами (if 23 == numberFromString {
)
Я сказал, что есть два оператора ==
, но на самом деле есть третья, которая позволяет вам поставить nil
в левую часть сравнения
if nil == name { ... }
Варианты именования
Существует нет соглашения Swift для именования необязательных типов по-разному, не относящихся к необязательным типам. Люди избегают добавлять что-то к имени, чтобы показать, что оно необязательно (например, "optionalMiddleName" или "possibleNumberAsString" ), и пусть объявление показывает, что это необязательный тип. Это становится затруднительным, если вы хотите назвать что-то, чтобы удерживать значение от опционального. Имя "middleName" означает, что это тип String, поэтому, когда вы извлекаете из него значение String, вы можете часто заканчивать такими именами, как "actualMiddleName" или "unwrappedMiddleName" или "realMiddleName". Используйте необязательную привязку и повторно используйте имя переменной, чтобы обойти это.
Официальное определение
От "Основы" на языке Swift для программирования:
Swift также вводит необязательные типы, которые обрабатывают отсутствие значения. Опционы говорят, что "есть значение, и оно равно x" или "вообще нет значения". Опционы аналогичны использованию nil с указателями в Objective-C, но они работают для любого типа, а не только для классов. Опционы более безопасны и более выразительны, чем nil-указатели в Objective-C, и являются основой большинства самых быстрых функций Swift.
Опционы - пример того, что Swift - это безопасный тип. Swift помогает вам четко понимать типы значений, с которыми может работать ваш код. Если часть вашего кода ожидает String, тип безопасности предотвращает передачу Int Int по ошибке. Это позволяет вам уловить и исправить ошибки как можно раньше в процессе разработки.
Чтобы закончить, вот стихотворение от 1899 года о вариантах:
Вчера на лестнице
Я встретил человека, которого там не было.
Сегодня он не был здесь
Я желаю, я желаю, чтобы он ушел
Antigonish