【问题标题】:When should I compare an optional value to nil?我什么时候应该将可选值与 nil 进行比较?
【发布时间】:2015-06-25 08:49:39
【问题描述】:

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

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

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

【问题讨论】:

  • 冗长?多年来一直在 C、Java 或 C++ 中使用相同的方法。
  • 长寿和冗长是正交的

标签: ios swift optional swift2


【解决方案1】:

检查可选项是否不是nil 几乎总是不必要的。几乎你唯一需要这样做的时候是它的nil-ness 是你想知道的唯一的东西——你不在乎价值是什么,只是它不是 @ 987654325@.

在大多数其他情况下,有一点 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 始终等于其他nils,并且小于任何非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) }

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

当你想要一个可选的时候,这很好。例如,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) }

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

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

【讨论】:

  • 虽然您提到了 nil 排序顺序,但构造“if i = 0 { }" 而不是在以后的编辑中。就我个人而言,我尝试始终将“if let i = i where i
  • 在 Chris Eidhof objc.io/books/advanced-swift987654321@ 的“Advanced Swift”中几乎逐字逐句地提到了很多内容
  • 这本书的作者之一几乎也写了这个。
  • 在 Swift 3 中删除了可选比较 - github.com/apple/swift-evolution/blob/master/proposals/…
【解决方案2】:

我认为你应该回到 Swift 编程书,了解这些东西的用途。 !当您绝对确定可选项不为零时使用。既然你声明你绝对确定,如果你错了它就会崩溃。这完全是故意的。从代码中的断言“不安全且最好避免”的意义上说,它是“不安全且最好避免的”。例如:

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

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

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

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

【讨论】:

  • 这个自我回答主要是为了回应if someOptional != nil { use someOptional } 在问题中不断出现的问题(甚至在一些流行的 Swift 教程书籍中)。当您希望程序在nil 上崩溃时,我也不同意使用! 是一种很好的技术。您最好使用带有明确错误消息的assert,而不是从! 得到的神秘错误错误。
  • 可能这不是正确的,我在发布此内容时做错了,但肯定需要对可选问题提供更多规范的答案,例如“我该怎么做”和类似的情况不断出现。
  • 第二个例子很可能会导致编译器错误,而不是实际的应用程序崩溃。
  • 这不是“绝对安全的”,因为不能保证您不会在块开头的某处不小心nil 将变量取出。您应该使用可选绑定。
【解决方案3】:

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

可选链是查询和调用属性的过程, 方法,以及当前可能为 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."

您可以查看全文here

【讨论】:

    【解决方案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,否则返回变量中包含的值。

      • 以下是使用示例:

        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
        
      • 现在您可以选择使用哪个运算符,两者的目的相同。

      【讨论】:

      • 你为什么假设一个空的初始化值(0""[]等)是合理的。选项的全部意义是迫使开发人员考虑这一点,并思考处理非值的明智方法是什么。例如。如果我有一个成绩计算应用程序,它取所有作业成绩的平均值,但尚未标记作业成绩,是否应该将学生显示为 0% 成绩?不!
      • @Alexander,您对 Swift 中的选项用法是正确的,但是此运算符用于不同的用法,大多数崩溃发生在强制向下转换或使用隐式展开选项时,以尽量减少这些类型的可以使用上面提到的操作符崩溃。开发人员还必须清楚何时使用运算符。运算符的使用是只提供初始化值来代替 nil,就是这样。谢谢亚历克斯:)
      • 1) 崩溃可能比允许传播无意义的数据更可取(事实上,故意对打开一个可选项导致致命错误进行了编程。它可能非常好在这种情况下,打开 Int? 只会告诉你那个内存位置有什么垃圾。但是,Swift 团队故意实施了引发致命错误的检查,以防止出现这种不确定的状态。
      • 2) 现有的 nil 合并运算符 (??) 已经满足了此运算符的目的。然而,关键区别在于,与您的 /*? 运算符不同,它不会代表程序员对正确的“回退值”做出任何假设。由他们自己决定。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多