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

时间:2015-04-18 12:15:44

标签: ios swift optional swift2

通常,您需要编写如下代码:

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

这看起来有点冗长,而且我听说使用!强制解包操作符可能是不安全的,最好避免使用。有没有更好的方法来解决这个问题?

5 个答案:

答案 0 :(得分:108)

几乎总是不必检查可选项是否nil。几乎是你需要这么做的唯一时间是它的nil - ness是你想知道的唯一的东西 - 你不关心价值中的东西,只是它的价值不是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

您可以使用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的可选性,但您通常可以推迟实际解开您的选项,直到很久以后,或者有时根本不打开。

顾名思义,你可以链接它们,所以你可以写:

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部分必须退出保护值的范围,例如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($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) }

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

当你想要一个可选的时候这很好。例如,reduce1reduce类似,但使用第一个值作为种子,如果数组为空则返回可选项。您可以这样写(使用前面的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

答案 1 :(得分:2)

我认为你应该回到Swift编程手册并了解这些内容的用途。 !当你完全确定可选的不是nil时使用。既然你宣称你是绝对肯定的,那么如果你错了就会崩溃。这完全是故意的。这是不安全的,最好的避免&#34;在某种意义上,你的代码中的断言是不安全的,最好的避免&#34;。例如:

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

!绝对安全。除非您的代码中存在重大错误,例如错误地写错(我希望您发现错误)

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

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

答案 2 :(得分:0)

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

  

可选链接是查询和调用属性的过程,   方法,以及当前可能为nil的可选项的下标。如果   optional包含值,属性,方法或下标调用   成功;如果optional是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

答案 3 :(得分:0)

我们可以使用可选绑定。

var x:Int?

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

答案 4 :(得分:0)

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

  • 创建一个新的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
    
  • 下面是变量包含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
    
  • 现在,您可以选择使用哪个运算符,两者的作用相同。