Этот ответ вики сообщества . Если вы чувствуете, что это может быть сделано лучше, не стесняйтесь редактировать это !
Фон: что необязательно?
В Swift Optional
- это универсальный тип, который может содержать значение (любого типа) или вообще не иметь значения.
Во многих других языках программирования определенное значение «страж» часто используется для указания на отсутствие значения . Например, nil
в Objective-C ( нулевой указатель ) указывает на отсутствие объекта. Но это становится более сложным при работе с примитивными типами - следует -1
использовать, чтобы указать отсутствие целого числа, или, возможно INT_MIN
, или какого-то другого целого числа? Если какое-либо конкретное значение выбрано для обозначения «нет целого числа», это означает, что оно больше не может рассматриваться как допустимое значение.
Swift - это типобезопасный язык, который означает, что он поможет вам понять типы значений, с которыми может работать ваш код. Если часть вашего кода ожидает строку, тип безопасности не позволяет вам передать ему 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 Optional не ограничиваются объектами / ссылочными типами. Optional ведет себя подобно Haskell Maybe .)
Почему я получил « фатальную ошибку: неожиданно обнаружил ноль при развертывании необязательного значения »?
Чтобы получить доступ к необязательному значению (если оно вообще есть), вам необходимо развернуть его. Необязательное значение может быть развернуто безопасно или принудительно. Если вы принудительно развернете необязательный параметр, у которого не было значения, ваша программа вылетит с сообщением, приведенным выше.
Xcode покажет вам сбой, выделив строку кода. Проблема возникает на этой линии.
Этот сбой может произойти с двумя различными видами принудительного развертывания:
1. Явное развертывание силы
Это делается с !
оператором по желанию. Например:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Неустранимая ошибка: неожиданно обнаружен ноль при развертывании необязательного значения
Как anOptionalString
и nil
здесь, вы получите сбой на линии, где вы принудительно разверните его.
2. Неявно развернутые необязательные
Они определены с !
, а не ?
после типа.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Предполагается, что эти опции содержат значение. Поэтому всякий раз, когда вы получаете доступ к неявно развернутому необязательному файлу, он будет автоматически развернут для вас. Если он не содержит значения, он потерпит крах.
print(optionalDouble) // <- CRASH
Неустранимая ошибка: неожиданно обнаружен ноль при неявном развертывании необязательного значения
Чтобы определить, какая переменная вызвала сбой, вы можете удерживать нажатой ⌥, чтобы показать определение, где вы можете найти необязательный тип.
В частности, IBOutlets обычно являются неявно развернутыми опциями. Это потому, что ваш xib или раскадровка свяжут розетки во время выполнения после инициализации. Поэтому вы должны убедиться, что у вас нет доступа к розеткам до их загрузки. Вы также должны проверить правильность соединений в вашем файле раскадровки / xib, в противном случае значения будут nil
во время выполнения и, следовательно, сбой, когда они неявно развернуты. , При исправлении соединений попробуйте удалить строки кода, которые определяют ваши розетки, а затем снова подключить их.
Когда я должен когда-либо принудительно развернуть Дополнительный?
Явная распаковка силы
Как правило, вы никогда не должны явно развертывать опционально с !
оператором. Могут быть случаи, когда использование !
приемлемо, но вы должны использовать его только в том случае, если вы на 100% уверены, что необязательный параметр содержит значение.
Хотя может быть случай, когда вы можете использовать принудительное развертывание, как вы знаете, для факта, что необязательный параметр содержит значение - нет ни одного места, где вы не можете безопасно развернуть это необязательное.
Неявно развернутые необязательные
Эти переменные разработаны таким образом, что вы можете отложить их назначение до следующего момента в вашем коде. Это ваша ответственность , чтобы убедиться , что они имеют значение , прежде чем получить доступ к ним. Однако, поскольку они включают принудительное развертывание, они по-прежнему небезопасны - поскольку они предполагают, что ваше значение не равно нулю, даже если присвоение nil допустимо.
Вы должны использовать только неявно развернутые опции как последнее средство . Если вы можете использовать переменную lazy или предоставить значение по умолчанию для переменной - вы должны сделать это вместо использования неявно развернутого необязательного параметра.
Тем не менее, есть несколько сценариев, в которых неявно развернутые дополнительные компоненты полезны , и вы все еще можете использовать различные способы их безопасного развертывания, как указано ниже, но вы всегда должны использовать их с должной осторожностью.
Как я могу безопасно иметь дело с Опциональными?
Самый простой способ проверить, содержит ли необязательное значение значение, - сравнить его с nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Тем не менее, в 99,9% случаев при работе с дополнительными компонентами вы фактически захотите получить доступ к содержащемуся в нем значению, если оно вообще содержится. Для этого вы можете использовать Optional Binding .
Дополнительное связывание
Опциональное связывание позволяет вам проверить, содержит ли опциональное значение значение, и позволяет назначить развернутое значение новой переменной или константе. Он использует синтаксис 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")
}
Это сначала проверяет, что необязательное содержит значение. Если это так , тогда значение unwrapped присваивается новой переменной ( number
), которую вы можете свободно использовать, как если бы она была необязательной. Если необязательный параметр не содержит значения, то будет вызываться предложение else, как и следовало ожидать.
Преимущество необязательного связывания заключается в том, что вы можете развернуть несколько необязательных элементов одновременно. Вы можете просто отделить утверждения запятой. Оператор будет успешным, если все дополнительные параметры были развернуты.
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
константе. Код после охраны продолжит выполнение. Если оно не содержит значения - охранник выполнит код в скобках, что приведет к передаче управления, так что код сразу после этого не будет выполнен.
Реальная изящная вещь в операторах guard - это то, что теперь развернутое значение доступно для использования в коде, который следует за оператором (поскольку мы знаем, что будущий код может выполняться только в том случае, если необязательное значение имеет значение). Это отлично подходит для устранения «пирамид гибели», созданных путем вложения нескольких операторов if.
Например:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Охранники также поддерживают те же хитрые приемы, которые поддерживал оператор if, такие как одновременное развертывание нескольких дополнительных опций и использование where
предложения.
Используете ли вы оператор if или guard, полностью зависит от того, требует ли какой-либо будущий код необязательного значения.
Оператор слияния
Коалесцентный Оператор Nil это отличный вариант стенографии в тройном условном операторе , в первую очередь предназначен для преобразования в УСТРОЙСТВО , не являющиеся дополнительные опции. Он имеет синтаксис a ?? b
, где a
это необязательный тип и b
тот же тип, что и a
(хотя обычно не необязательный).
По сути, он позволяет вам сказать: «Если a
содержит значение, разверните его. Если этого не произойдет, тогда вернитесь b
вместо этого ». Например, вы можете использовать это так:
let number = anOptionalInt ?? 0
Это определит number
константу Int
типа, которая будет либо содержать значение anOptionalInt
, если оно содержит значение, либо 0
иным образом.
Это просто сокращение для:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Опциональная цепочка
Вы можете использовать Optional Chaining , чтобы вызвать метод или получить доступ к свойству по желанию. Это просто делается путем добавления суффикса к имени переменной при его ?
использовании.
Например, допустим, у нас есть переменная 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
, мы можем использовать allow 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?
, в тех редких случаях, когда обработка ошибки не важна.
Ресурсы