Когда я должен сравнить необязательное значение с нулем?
Довольно часто вам нужно написать такой код:
if someOptional != nil {
// do something with the unwrapped someOptional e.g.
someFunction(someOptional!)
}
Это кажется немного многословным, а также я слышу, что с помощью !
Оператор принудительного развертывания может быть небезопасным и его лучше избегать. Есть ли лучший способ справиться с этим?
5 ответов
Почти всегда нет необходимости проверять, является ли дополнительный nil
, Практически единственный раз, когда вам нужно сделать это, если его nil
единственная вещь, о которой вы хотите знать, - это то, что вы цените. nil
,
При большинстве других обстоятельств есть небольшая стенография Swift, которая может более безопасно и кратко выполнить задачу внутри if
для тебя.
Использование значения, если оно не nil
Вместо:
let s = "1"
let i = Int(s)
if i != nil {
print(i! + 1)
}
ты можешь использовать if let
:
if let i = Int(s) {
print(i + 1)
}
Вы также можете использовать var
:
if var i = Int(s) {
print(++i) // prints 2
}
но обратите внимание, что i
будет локальной копией - любые изменения в i
не повлияет на значение внутри оригинала необязательно.
Вы можете развернуть несколько опций в одном if let
и более поздние могут зависеть от более ранних:
if let url = NSURL(string: urlString),
data = NSData(contentsOfURL: url),
image = UIImage(data: data)
{
let view = UIImageView(image: image)
// etc.
}
Вы также можете добавить where
предложения к развернутым значениям:
if let url = NSURL(string: urlString) where url.pathExtension == "png",
let data = NSData(contentsOfURL: url), image = UIImage(data: data)
{ etc. }
Замена nil
с дефолтом
Вместо:
let j: Int
if i != nil {
j = i
}
else {
j = 0
}
или же:
let j = i != nil ? i! : 0
Вы можете использовать оператор ноль-коалесценции, ??
:
// j will be the unwrapped value of i,
// or 0 if i is nil
let j = i ?? 0
Приравнивание необязательного к необязательному
Вместо:
if i != nil && i! == 2 {
print("i is two and not nil")
}
Вы можете проверить, равны ли необязательные значения не необязательным значениям:
if i == 2 {
print("i is two and not nil")
}
Это также работает со сравнениями:
if i < 5 { }
nil
всегда равен другому nil
и меньше, чем любой nil
значение.
Быть осторожен! Здесь могут быть ошибки:
let a: Any = "hello"
let b: Any = "goodbye"
if (a as? Double) == (b as? Double) {
print("these will be equal because both nil...")
}
Вызов метода (или чтение свойства) для необязательного
Вместо:
let j: Int
if i != nil {
j = i.successor()
}
else {
// no reasonable action to take at this point
fatalError("no idea what to do now...")
}
Вы можете использовать дополнительные цепочки, ?.
:
let j = i?.successor()
Заметка, j
теперь также будет необязательным, чтобы учесть fatalError
сценарий. Позже вы можете использовать один из других методов в этом ответе для обработки j
Это опция, но вы можете отложить развертывание ваших опций намного позже, а иногда и вовсе нет.
Как видно из названия, вы можете связать их, так что вы можете написать:
let j = s.toInt()?.successor()?.successor()
Необязательное сцепление также работает с подписками:
let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]]
let sevenOfNine = dictOfArrays["nine"]?[7] // returns {Some 7}
и функции:
let dictOfFuncs: [String:(Int,Int)->Int] = [
"add":(+),
"subtract":(-)
]
dictOfFuncs["add"]?(1,1) // returns {Some 2}
Присвоение недвижимости по желанию
Вместо:
if splitViewController != nil {
splitViewController!.delegate = self
}
Вы можете назначить через необязательную цепочку:
splitViewController?.delegate = self
Только если splitViewController
не является nil
произойдет ли назначение.
Использование значения, если оно не nil
или освобождение под залог (новое в Swift 2.0)
Иногда в функции есть небольшой фрагмент кода, который вы хотите написать, чтобы проверить необязательный параметр, и если nil
, выйдите из функции рано, в противном случае продолжайте.
Вы можете написать это так:
func f(s: String) {
let i = Int(s)
if i == nil { fatalError("Input must be a number") }
print(i! + 1)
}
или чтобы избежать распаковки, вот так:
func f(s: String) {
if let i = Int(s) {
print(i! + 1)
}
else {
fatalErrr("Input must be a number")
}
}
но гораздо приятнее держать код обработки ошибок в верхней части чека. Это также может привести к неприятному гнездованию ("пирамида гибели").
Вместо этого вы можете использовать guard
, который похож на if not let
:
func f(s: String) {
guard let i = Int(s)
else { fatalError("Input must be a number") }
// i will be an non-optional Int
print(i+1)
}
else
часть должна выйти из области охраняемого значения, например return
или же fatalError
, чтобы гарантировать, что охраняемое значение будет действительным для остальной части области.
guard
не ограничен областью действия функции. Например следующее:
var a = ["0","1","foo","2"]
while !a.isEmpty {
guard let i = Int(a.removeLast())
else { continue }
print(i+1, appendNewline: false)
}
печать 321
,
Цикл по ненулевым элементам в последовательности (новое в Swift 2.0)
Если у вас есть последовательность опций, вы можете использовать for case let _?
перебрать все необязательные элементы:
let a = ["0","1","foo","2"]
for case let i? in a.map({ Int($0)}) {
print(i+1, appendNewline: false)
}
печать 321
, Это использует синтаксис сопоставления с образцом для необязательного, который является именем переменной, сопровождаемой ?
,
Вы также можете использовать этот шаблон сопоставления в switch
заявления:
func add(i: Int?, _ j: Int?) -> Int? {
switch (i,j) {
case (nil,nil), (_?,nil), (nil,_?):
return nil
case let (x?,y?):
return x + y
}
}
add(1,2) // 3
add(nil, 1) // nil
Цикл пока функция не вернется nil
Так же, как if let
Вы также можете написать while let
и цикл до nil
:
while let line = readLine() {
print(line)
}
Вы также можете написать while var
(аналогичные предупреждения if var
применять).
where
здесь также работают предложения (и завершают цикл, а не пропускают):
while let line = readLine()
where !line.isEmpty {
print(line)
}
Передача необязательного в функцию, которая принимает необязательный и возвращает результат
Вместо:
let j: Int
if i != nil {
j = abs(i!)
}
else {
// no reasonable action to take at this point
fatalError("no idea what to do now...")
}
Вы можете использовать дополнительные map
оператор:
let j = i.map { abs($0) }
Это очень похоже на необязательное сцепление, но для случая, когда вам необходимо передать не необязательное значение в функцию в качестве аргумента. Как и в случае необязательного связывания, результат будет необязательным.
Это хорошо, когда вы хотите дополнительный в любом случае. Например, reduce1
как reduce
, но использует первое значение в качестве начального числа, возвращая необязательное значение, если массив пуст. Вы можете написать это так (используя guard
Ключевое слово из ранее):
extension Array {
func reduce1(combine: (T,T)->T)->T? {
guard let head = self.first
else { return nil }
return dropFirst(self).reduce(head, combine: combine)
}
}
[1,2,3].reduce1(+) // returns 6
Но вместо этого вы могли бы map
.first
собственности, и вернуть, что:
extension Array {
func reduce1(combine: (T,T)->T)->T? {
return self.first.map {
dropFirst(self).reduce($0, combine: combine)
}
}
}
Передача необязательного значения в функцию, которая принимает необязательное значение и возвращает результат, избегая назойливых двойных опционалов
Иногда вы хотите что-то похожее на map
, но функция, которую вы хотите вызвать сама, возвращает необязательное значение. Например:
// an array of arrays
let arr = [[1,2,3],[4,5,6]]
// .first returns an optional of the first element of the array
// (optional because the array could be empty, in which case it's nil)
let fst = arr.first // fst is now [Int]?, an optional array of ints
// now, if we want to find the index of the value 2, we could use map and find
let idx = fst.map { find($0, 2) }
Но сейчас idx
имеет тип Int??
, двойной необязательный. Вместо этого вы можете использовать flatMap
, который "сплющивает" результат в один необязательный:
let idx = fst.flatMap { find($0, 2) }
// idx will be of type Int?
// and not Int?? unlike if `map` was used
Я думаю, что вы должны вернуться к книге по программированию Swift и узнать, для чего эти вещи.! используется, когда вы абсолютно уверены, что необязательный не ноль. Так как вы заявили, что абсолютно уверены, он падает, если вы не правы. Что является полностью преднамеренным. Это "небезопасно и лучше избегать" в том смысле, что утверждения в вашем коде "небезопасны и лучше избегать". Например:
if someOptional != nil {
someFunction(someOptional!)
}
! абсолютно безопасно. Если в вашем коде нет большой ошибки, наподобие написания по ошибке (надеюсь, вы заметите ошибку)
if someOptional != nil {
someFunction(SomeOptional!)
}
в этом случае может произойти сбой вашего приложения, вы исследуете причину сбоя и исправляете ошибку - именно для этого и существует сбой. Одна из целей Swift состоит в том, что, очевидно, ваше приложение должно работать правильно, но поскольку Swift не может обеспечить это, оно гарантирует, что ваше приложение либо работает корректно, либо, по возможности, дает сбой, поэтому ошибки удаляются до его отправки.
У вас есть один способ. Это называется необязательной цепочкой. Из документации:
Необязательное сцепление - это процесс запроса и вызова свойств, методов и подписок для необязательного элемента, который в настоящее время может быть равен нулю. Если необязательный параметр содержит значение, вызов свойства, метода или индекса не выполняется; если необязательным является nil, вызов свойства, метода или нижнего индекса возвращает nil. Несколько запросов могут быть объединены в цепочку, и вся цепочка завершится неудачно, если какое-либо звено в цепочке равно нулю.
Вот пример
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."
Вы можете проверить полную статью здесь.
Мы можем использовать необязательную привязку.
var x:Int?
if let y = x {
// x was not nil, and its value is now stored in y
}
else {
// x was nil
}
После долгих размышлений и исследований я нашел самый простой способ развернуть опционально:
Создайте новый файл Swift и назовите его UnwrapOperator.swift
Вставьте следующий код в файл:
import Foundation import UIKit protocol OptionalType { init() } extension String: OptionalType {} extension Int: OptionalType {} extension Int64: OptionalType {} extension Float: OptionalType {} extension Double: OptionalType {} extension CGFloat: OptionalType {} extension Bool: OptionalType {} extension UIImage : OptionalType {} extension IndexPath : OptionalType {} extension NSNumber : OptionalType {} extension Date : OptionalType {} extension UIViewController : OptionalType {} postfix operator *? postfix func *?<T: OptionalType>( lhs: T?) -> T { guard let validLhs = lhs else { return T() } return validLhs } prefix operator / prefix func /<T: OptionalType>( rhs: T?) -> T { guard let validRhs = rhs else { return T() } return validRhs }
Теперь в приведенном выше коде создано 2 оператора [один префикс и один постфикс].
- Во время распаковки вы можете использовать любой из этих операторов до или после дополнительных
Объяснение простое: операторы возвращают значение конструктора, если они получают значение nil в переменной, иначе значение, содержащееся в переменной.
Ниже приведен пример использования:
var a_optional : String? = "abc" var b_optional : Int? = 123 // before the usage of Operators print(a_optional) --> Optional("abc") print(b_optional) --> Optional(123) // Prefix Operator Usage print(/a_optional) --> "abc" print(/b_optional) --> 123 // Postfix Operator Usage print(a_optional*?) --> "abc" print(b_optional*?) --> 123
Ниже приведен пример, когда переменная содержит ноль:
var a_optional : String? = nil var b_optional : Int? = nil // before the usage of Operators print(a_optional) --> nil print(b_optional) --> nil // Prefix Operator Usage print(/a_optional) --> "" print(/b_optional) --> 0 // Postfix Operator Usage print(a_optional*?) --> "" print(b_optional*?) --> 0
Теперь вы сами выбираете, какой оператор вы используете, оба служат одной и той же цели.