Когда следует сравнивать необязательное значение с nil?

Довольно часто вам нужно написать код, например:

if someOptional != nil {
    // do something with the unwrapped someOptional e.g.       
    someFunction(someOptional!)
}

Это выглядит немного многословным, и я также слышу, что использование оператора ! force unwrap может быть небезопасным и лучше всего избегать. Есть ли лучший способ справиться с этим?

Ответ 1

Почти всегда нет необходимости проверять, не является ли опциональным nil. В значительной степени единственный раз, когда вам нужно это сделать, если его nil -ness - это единственное, о чем вы хотите знать - вам не важно, в чем ценность, просто это не nil.

В большинстве других случаев существует немного сокращенного сокращения, которое может более безопасно и кратко выполнить задачу внутри 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

вы можете использовать оператор nil-coalescing, ??:

// 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 s и меньше любого значения не 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 s, но вы можете часто откладывать фактическое развертывание своих опций до гораздо позже или иногда не на всех.

Как следует из названия, вы можете связать их, чтобы вы могли написать:

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, или bailing (новое в 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 должна выйти из области защищенного значения, например. a 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 operator:

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 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??, double-optional. Вместо этого вы можете использовать flatMap, который "выравнивает" результат в один дополнительный параметр:

let idx = fst.flatMap { find($0, 2) }
// idx will be of type Int? 
// and not Int?? unlike if `map` was used

Ответ 2

Я думаю, вы должны вернуться к книге программирования Swift и узнать, для чего предназначены эти вещи.! используется, когда вы абсолютно уверены, что необязательный параметр не равен нулю. Поскольку вы заявили, что абсолютно уверены, что он падает, если вы ошибаетесь. Это совершенно намеренно. Это "небезопасно и лучше избегать" в том смысле, что утверждения в вашем коде "небезопасны и лучше всего избегаются". Например:

if someOptional != nil {
    someFunction(someOptional!)
}

The! абсолютно безопасен. Если в коде нет большой ошибки, например, написания по ошибке (надеюсь, вы заметили ошибку)

if someOptional != nil {
    someFunction(someOptional!)
}

и в этом случае ваше приложение может потерпеть крах, вы исследуете, почему он сбой, и вы исправляете ошибку - это именно то, для чего существует авария. Одна из целей в Swift заключается в том, что, очевидно, ваше приложение должно работать правильно, но поскольку Swift не может обеспечить его выполнение, он гарантирует, что ваше приложение либо работает правильно, либо, если возможно, сбой, поэтому ошибки удаляются до того, как приложение будет отправлено.

Ответ 3

У вас есть один путь. Он называется Необязательный цепочек. Из документации:

Необязательная цепочка - это процесс для запросов и вызовов, методы и индексы на необязательном, который в настоящее время может быть равен нулю. Если необязательный параметр содержит значение, свойство, метод или вызов подстроки успешно; если необязательный параметр равен нулю, свойство, метод или индекс call возвращает nil. Несколько запросов можно связать вместе, а целая цепочка изящно выходит из строя, если какая-либо ссылка в цепочке равна нулю.

Вот пример

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

if let roomCount = john.residence?.numberOfRooms {
    println("John residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."

Вы можете проверить полную статью здесь.

Ответ 4

Мы можем использовать необязательную привязку.

var x:Int?

if let y = x {
  // x was not nil, and its value is now stored in y
}
else {
  // x was nil
}

Ответ 5

После долгих размышлений и исследований я придумал самый простой способ развернуть дополнительный вариант:

  • Создайте новый файл 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 в переменной else, содержащее значение внутри переменной.

  • Ниже приведен пример использования:

    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
    
  • Ниже приведен пример, когда переменная содержит nil:

    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
    
  • Теперь вы выбираете, какой оператор вы используете, оба служат той же цели.