我什么时候应该将可选值与 nil 进行比较? [英] When should I compare an optional value to nil?

查看:27
本文介绍了我什么时候应该将可选值与 nil 进行比较?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

if someOptional != nil {//用解开的 someOptional 做一些事情,例如someFunction(someOptional!)}

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

解决方案

检查一个可选项是否不是 nil 几乎总是没有必要的.几乎唯一需要这样做的时间是如果它的 nil-ness 是您唯一 想知道的事情——您不关心值中的内容,只是它不是nil.

在大多数其他情况下,有一些 Swift 速记可以更安全、更简洁地为您完成 if 中的任务.

如果值不是nil

,则使用该值

代替:

let s = "1"让我 = Int(s)如果我 != nil {打印(我!+ 1)}

你可以使用if let:

if let i = Int(s) {打印(我 + 1)}

你也可以使用var:

if var i = Int(s) {print(++i)//打印 2}

但请注意,i 将是本地 副本 - 对 i 的任何更改都不会影响原始可选中的值.

你可以在一个 if let 中解开多个选项,后面的可以依赖前面的:

if let url = NSURL(string: urlString),数据 = NSData(contentsOfURL: url),图像 = UIImage(数据:数据){让视图 = UIImageView(图像:图像)//等等.}

您还可以将 where 子句添加到解包值中:

if let url = NSURL(string: urlString) where url.pathExtension == "png",让数据 = NSData(contentsOfURL: url), image = UIImage(data: data){ 等等. }

用默认值替换nil

代替:

let j: Int如果我 != nil {j = i}别的 {j = 0}

或:

让 j = i != nil ?一世!: 0

你可以使用 nil-coalescing 操作符,??:

//j 将是 i 的解包值,//如果 i 为零,则为 0让 j = i ??0

将可选的等同于非可选的

代替:

if i != nil &&一世!== 2 {打印(我是两个而不是零")}

您可以检查可选值是否等于非可选值:

if i == 2 {打印(我是两个而不是零")}

这也适用于比较:

if i <5 { }

nil 总是等于其他 nil ,并且小于任何非 nil 值.

小心!这里可能有问题:

let a: Any = "hello"让 b: Any = "再见"if (a as?Double) == (b as?Double) {打印(这些将是相等的,因为两者都为零......")}

调用可选的方法(或读取属性)

代替:

let j: Int如果我 != nil {j = i.successor()}别的 {//在这一点上没有采取合理的行动致命错误(不知道现在该做什么……")}

你可以使用可选链,?.:

let j = i?.successor()

注意,j 现在也将是可选的,以解决 fatalError 场景.稍后,您可以使用本答案中的其他技术之一来处理 j 的可选性,但您通常可以将实际展开您的可选性推迟到更晚,有时甚至根本不展开.

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

让 j = s.toInt()?.successor()?.successor()

可选链接也适用于下标:

let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]]let SevenOfNine = dictOfArrays["nine"]?[7]//返回 {Some 7}

和函数:

let dictOfFuncs: [String:(Int,Int)->Int] = ["添加":(+),减去":(-)]dictOfFuncs["add"]?(1,1)//返回 {Some 2}

分配给可选的属性

代替:

if splitViewController != nil {splitViewController!.delegate = self}

您可以通过分配一个可选链:

splitViewController?.delegate = self

仅当 splitViewControllernil 时,才会进行赋值.

如果值不是nil,则使用该值,或者使用该值(Swift 2.0 中的新功能)

有时在一个函数中,你想写一小段代码来检查一个可选项,如果它是nil,请提前退出该函数,否则继续.

你可以这样写:

func f(s: String) {让我 = Int(s)if i == nil { fatalError("输入必须是数字") }打印(我!+ 1)}

或者为了避免强制展开,像这样:

func f(s: String) {如果让我 = Int(s) {打印(我!+ 1)}别的 {fatalErrr("输入必须是数字")}}

但是通过检查将错误处理代码保留在顶部要好得多.这也可能导致令人不快的嵌套(厄运金字塔").

相反,您可以使用 guard,这类似于 if not let:

func f(s: String) {守卫让我 = Int(s)else {fatalError("输入必须是数字")}//我将是一个非可选的 Int打印(i+1)}

else 部分必须退出保护值的范围,例如returnfatalError,以保证受保护的值对作用域的其余部分有效.

guard 不限于函数作用域.例如以下内容:

var a = ["0","1","foo","2"]而 !a.isEmpty {守卫让我 = Int(a.removeLast())否则{继续}打印(i+1, appendNewline: false)}

打印321.

循环遍历序列中的非 nil 项(Swift 2.0 中的新功能)

如果你有一个可选的序列,你可以使用 for case let _? 来迭代所有的非可选元素:

let a = ["0","1","foo","2"]对于情况让我?在 a.map({ Int($0)}) {打印(i+1, appendNewline: false)}

打印321.这是对可选的使用模式匹配语法,它是一个变量名,后跟 ?.

你也可以在 switch 语句中使用这种模式匹配:

func add(i: Int?, _ j: Int?) ->内部?{开关(i,j){case (nil,nil), (_?,nil), (nil,_?):返回零case let (x?,y?):返回 x + y}}添加(1,2)//3add(nil, 1)//无

循环直到函数返回nil

很像if let,你也可以写while let并循环直到nil:

while let line = readLine() {打印(行)}

您也可以编写 while var(类似于 if var apply 的警告).

where 子句在这里也有效(并终止循环,而不是跳过):

while let line = readLine()其中 !line.isEmpty {打印(行)}

将一个可选项传递给一个接受非可选项并返回结果的函数

代替:

let j: Int如果我 != nil {j = abs(i!)}别的 {//在这一点上没有采取合理的行动致命错误(不知道现在该做什么……")}

你可以使用 optional 的 map 操作符:

let j = i.map { abs($0) }

这与可选链非常相似,但是当您需要将非可选值作为参数传入函数时.与可选链一样,结果也是可选的.

当你想要一个可选的时候这很好.例如,reduce1 类似于 reduce,但使用第一个值作为种子,如果数组为空,则返回一个可选值.你可以这样写(使用之前的 guard 关键字):

扩展数组{func reduce1(combine: (T,T)->T)->T?{守卫让头= self.first否则{返回零}返回 dropFirst(self).reduce(head, combine: combine)}}[1,2,3].reduce1(+)//返回 6

但是你可以map .first 属性,然后返回:

扩展数组{func reduce1(combine: (T,T)->T)->T?{返回 self.first.map {dropFirst(self).reduce($0, combine: combine)}}}

将可选项传递给一个接受可选项并返回结果的函数,避免恼人的双选项

有时,您想要类似于 map 的东西,但是您要调用本身 的函数返回一个可选项.例如:

//数组数组让 arr = [[1,2,3],[4,5,6]]//.first 返回数组第一个元素的可选值//(可选,因为数组可能为空,在这种情况下它为零)let fst = arr.first//fst 现在是 [Int]?,一个可选的整数数组//现在,如果我们想找到值 2 的索引,我们可以使用 map 和 find让 idx = fst.map { find($0, 2) }

但是现在 idxInt?? 类型,一个双可选.相反,您可以使用 flatMap,它将结果扁平化"为单个可选:

let idx = fst.flatMap { find($0, 2) }//idx 将是 Int 类型?//而不是 Int??不像如果使用`map`

Quite often, you need to write code such as the following:

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

This seems a bit verbose, and also I hear that using the ! force unwrap operator can be unsafe and best avoided. Is there a better way to handle this?

解决方案

It is almost always unnecessary to check if an optional is not nil. Pretty much the only time you need to do this is if its nil-ness is the only thing you want to know about – you don’t care what’s in the value, just that it’s not nil.

Under most other circumstances, there is a bit of Swift shorthand that can more safely and concisely do the task inside the if for you.

Using the value if it isn’t nil

Instead of:

let s = "1"
let i = Int(s)

if i != nil {
    print(i! + 1)
}

you can use if let:

if let i = Int(s) {
    print(i + 1)
}

You can also use var:

if var i = Int(s) {
    print(++i)  // prints 2
}

but note that i will be a local copy - any changes to i will not affect the value inside the original optional.

You can unwrap multiple optionals within a single if let, and later ones can depend on earlier ones:

if let url = NSURL(string: urlString),
       data = NSData(contentsOfURL: url),
       image = UIImage(data: data)
{
    let view = UIImageView(image: image)
    // etc.
}

You can also add where clauses to the unwrapped values:

if let url = NSURL(string: urlString) where url.pathExtension == "png",
   let data = NSData(contentsOfURL: url), image = UIImage(data: data)
{ etc. }

Replacing nil with a default

Instead of:

let j: Int
if i != nil {
    j = i
}
else {
    j = 0
}

or:

let j = i != nil ? i! : 0

you can use the nil-coalescing operator, ??:

// j will be the unwrapped value of i,
// or 0 if i is nil
let j = i ?? 0

Equating an optional with a non-optional

Instead of:

if i != nil && i! == 2 {
    print("i is two and not nil")
}

you can check if optionals are equal to non-optional values:

if i == 2 {
    print("i is two and not nil")
}

This also works with comparisons:

if i < 5 { }

nil is always equal to other nils, and is less than any non-nil value.

Be careful! There can be gotchas here:

let a: Any = "hello"
let b: Any = "goodbye"
if (a as? Double) == (b as? Double) {
    print("these will be equal because both nil...")
}

Calling a method (or reading a property) on an optional

Instead of:

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...")
}

you can use optional chaining, ?.:

let j = i?.successor()

Note, j will also now be optional, to account for the fatalError scenario. Later, you can use one of the other techniques in this answer to handle j’s optionality, but you can often defer actually unwrapping your optionals until much later, or sometimes not at all.

As the name implies, you can chain them, so you can write:

let j = s.toInt()?.successor()?.successor()

Optional chaining also works with subscripts:

let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]]
let sevenOfNine = dictOfArrays["nine"]?[7]  // returns {Some 7}

and functions:

let dictOfFuncs: [String:(Int,Int)->Int] = [
      "add":(+),
      "subtract":(-)
]

dictOfFuncs["add"]?(1,1)  // returns {Some 2}

Assigning to a property on an optional

Instead of:

if splitViewController != nil {
    splitViewController!.delegate = self 
}

you can assign through an optional chain:

splitViewController?.delegate = self

Only if splitViewController is non-nil will the assignment happen.

Using the value if it isn’t nil, or bailing (new in Swift 2.0)

Sometimes in a function, there’s a short bit of code you want to write to check an optional, and if it’s nil, exit the function early, otherwise keep going.

You might write this like this:

func f(s: String) {
    let i = Int(s)
    if i == nil { fatalError("Input must be a number") }
    print(i! + 1)
}

or to avoid the force unwrap, like this:

func f(s: String) {
    if let i = Int(s) {
        print(i! + 1)
    }
    else { 
        fatalErrr("Input must be a number")
    }
}

but it’s much nicer to keep the error-handling code at the top by the check. This can also lead to unpleasant nesting (the "pyramid of doom").

Instead you can use guard, which is like an 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)
}

The else part must exit the scope of the guarded value, e.g. a return or fatalError, to guarantee that the guarded value will be valid for the remainder of the scope.

guard isn’t limited to function scope. For example the following:

var a = ["0","1","foo","2"]
while !a.isEmpty  {
    guard let i = Int(a.removeLast())
        else { continue }

    print(i+1, appendNewline: false)
}

prints 321.

Looping over non-nil items in a sequence (new in Swift 2.0)

If you have a sequence of optionals, you can use for case let _? to iterate over all the non-optional elements:

let a = ["0","1","foo","2"]
for case let i? in a.map({ Int($0)}) {
    print(i+1, appendNewline: false)
}

prints 321. This is using the pattern-matching syntax for an optional, which is a variable name followed by ?.

You can also use this pattern matching in switch statements:

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

Looping until a function returns nil

Much like if let, you can also write while let and loop until nil:

while let line = readLine() {
    print(line)
}

You can also write while var (similar caveats to if var apply).

where clauses also work here (and terminate the loop, rather than skipping):

while let line = readLine() 
where !line.isEmpty {
    print(line)
}

Passing an optional into a function that takes a non-optional and returns a result

Instead of:

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...")
}

you can use optional’s map operator:

let j = i.map { abs($0) }

This is very similar to optional chaining, but for when you need to pass the non-optional value into the function as an argument. As with optional chaining, the result will be optional.

This is nice when you want an optional anyway. For example, reduce1 is like reduce, but uses the first value as the seed, returning an optional in case the array is empty. You might write it like this (using the guard keyword from earlier):

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

But instead you could map the .first property, and return that:

extension Array {
    func reduce1(combine: (T,T)->T)->T? {
        return self.first.map {
            dropFirst(self).reduce($0, combine: combine)
        }
    }
}

Passing an optional into a function that takes an optional and returns a result, avoiding annoying double-optionals

Sometimes, you want something similar to map, but the function you want to call itself returns an optional. For example:

// 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) }

But now idx is of type Int??, a double-optional. Instead, you can use flatMap, which "flattens" the result into a single optional:

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

这篇关于我什么时候应该将可选值与 nil 进行比较?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆