什么时候应该将可选值与 nil 进行比较?

When should I compare an optional value to nil?

很多时候,您需要编写如下代码:

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

这似乎有点冗长,而且我听说使用 ! 强制展开运算符可能不安全,最好避免使用。有没有更好的方法来处理这个问题?

几乎总是没有必要检查一个可选项是否不是 nil。几乎唯一需要这样做的情况是,如果它的 nil-ness 是 唯一 你想知道的事情——你不关心值是什么,只是它不是 nil.

在大多数其他情况下,有一点Swift shorthand可以更安全简洁地为您完成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 将是 local 副本 - 对 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,并且小于任何非 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 或 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")
    }
}

但是通过检查将错误处理代码保留在顶部会更好。这也会导致不愉快的嵌套("pyramid of doom")。

您可以使用 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部分必须退出保护值的范围,例如returnfatalError,以保证受保护的值在范围的其余部分有效。

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([=33=])}) {
    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

循环直到函数 returns 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)
}

将可选参数传递给采用非可选参数且 return 结果为

的函数

而不是:

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([=38=]) }

这与可选链接非常相​​似,但是当您需要将非可选值 传递给 函数作为参数时。与可选链接一样,结果将是可选的。

当你想要一个可选的时候,这很好。例如,reduce1 类似于 reduce,但使用第一个值作为种子,return 为可选值以防数组为空。你可以这样写(使用前面的 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 属性,并且 return 那:

extension Array {
    func reduce1(combine: (T,T)->T)->T? {
        return self.first.map {
            dropFirst(self).reduce([=40=], combine: combine)
        }
    }
}

将可选参数传递给接受可选参数和 return 结果的函数,避免烦人的双重可选参数

有时,您想要类似于 map 的东西,但您要调用的函数 本身 return 是可选的。例如:

// 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([=41=], 2) }

但是现在idxInt??类型的,一个double-optional。相反,您可以使用 flatMap,它将结果“扁平化”为一个可选的:

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

你有一个办法。它被称为Optional Chaining。来自文档:

Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is 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."

您可以查看完整文章 here

我们可以使用可选绑定。

var x:Int?

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

我认为你应该回到 Swift 编程书籍并了解这些东西的用途。 !当您绝对确定可选项不为零时使用。因为你声明你绝对确定,所以如果你错了它会崩溃。这完全是故意的。从某种意义上说,它是 "unsafe and best avoided",您的代码中的断言是 "unsafe and best avoided"。例如:

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

!绝对安全。除非你的代码有很大的错误,比如写错了(希望你能发现错误)

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

在这种情况下,您的应用程序可能会崩溃,您调查它崩溃的原因,然后修复错误 - 这正是崩溃的原因。 Swift 中的一个目标显然是您的应用程序应该正常运行,但由于 Swift 无法强制执行此操作,它会强制您的应用程序正常运行或崩溃(如果可能),以便在应用程序发布之前消除错误。

经过大量的思考和研究,我想出了最简单的方法来打开一个可选的:

  • 创建一个新的 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个运算符[一前缀一后缀]。

  • 在解包时,您可以在可选项之前或之后使用这些运算符中的任何一个
  • 解释很简单,运算符 returns 构造函数值 如果它们在变量中得到 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
    
  • 下面是 变量包含 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
    
  • 现在您可以选择使用哪个运算符,两者的用途相同。