美文网首页iOS劝退指南
Swift 4 Optionals 可选值

Swift 4 Optionals 可选值

作者: 艺术农 | 来源:发表于2018-06-04 13:08 被阅读430次

什么是可选值类型

Swift introduces a whole new type, optional, that handles the possibility a value could be nil. If you’re handling a non-optional type, then you’re guaranteed to have a value and don’t need to worry about the existence of a valid value. Similarly, if you are using an optional type then you know you must handle the nil case. It removes the ambiguity introduced by using sentinel values

看下官方解释:

Swift创造了一个全新类型:可选值,用来处理数据的值可能为空的情况。

首先,我们先引入 Sentinel values的概念。

Sentinel values(翻译成警戒值吗)

警戒值的定义:警戒值代表一种特殊的情况,比如某个值为空的时候,这个时候的值就是警戒值。

A valid value that represents a special condition such as the absence of a value is known as a sentinel value.

这里举个栗子来解释,假如我们从服务器请求某些数据的时候,我们用一个变量来接收服务器的错误码。

var errorCode = 0

在请求成功的情况下,我们用0来表示没有错误返回,那么这个时候0就是一个Sentinel value。但是对于程序员来说,0是一个具有相当迷惑性的值。因为在未来的某些时候,0可能就是一个真实的错误码。在不参考相关文档的时候,我们不能十分确定,0就是成功的情况。

在上面这种情况发生时,我们会想是否能有一种特殊的类型,能够表示一个变量有值或无值的两种情况呢?

在其他的语言中也大量使用到了警戒值,例如在OC中有nil的概念。但是,在OC中nil也仅仅是zero的同义词,换句话说就是另一个警戒值罢了。

为了解决这个问题,Swift创造一个全新的类型:可选值,专门用来处理数据可能为空的情况。

It removes the ambiguity introduced by using sentinel values.

使用可选值类型,我们就可以消除警戒值带来的歧义问题。

optionals介绍

想象可选值类型就是一个盒子,这个盒子有两种情况,一种是填充了值,一种是为空,没有填充任何值。当它没有填充值的时候,我们称之为nil。这里我们需要注意的是,盒子自始至终都是存在的,变化的只有盒子的内容。

  • 语法形式
var errorCode : Int?
errorCode = 100
errorCode = nil

我们在类型后添加一个问号来标识这个数据是可选值类型,表明这个变量盒子包含了两种可能性:有值或者为空。专业描述起来就是,这个可选值变量对有值或无值进行了打包(wrap)操作。

Unwarpping optionals(解包)

在可选值类型打包完后,实际使用的时候如何获取盒子(box)里的内容呢?
首先看下下面代码的执行结果

var result: Int? = 30
print(result)

执行结果:

Optional(30)

与此同时,你会在看到一条警告,“Expression implicitly coerced from 'Int?' to Any”.意思警告你,你把可选值类型隐式地强制转换成任意类型来处理,这就意味着你的代码可能出现了错误。屏蔽警告你可以修改代码为print(result as any)

警告可能还不够直白的看出可选值和非可选值的区别,下面我们尝试下对可选值直接做操作:

print(result + 1)

上面的代码会触发一个错误

Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?

为什么报错,因为我们直接对一个可选值盒子做了加一操作,而不是对可选值盒子里面的内容做加一操作,这肯定是错误的。

Force unwrapping (强制解包)

上面的错误已经直白的告诉我们,可选值没有进行解包(unwrapped),那么我们解包即可:

var authorName: String? = "Matt Galloway"
var authorAge: Int? = 30

有两种方法可以解包,首先看下第一种方法:强制解包

var unwrappedAuthorName = authorName!
print("Author is \(unwrappedAuthorName)")

打印:

Author is Matt Galloway

好了,我们的目的达到了~
变量名后的感叹号告诉编译器,我想看下盒子里面的内容并取出来里面的值。但是,使用强制解包对程序可能会有点小危险,我们应该谨慎的使用。
看下下面的代码就知道了:

authorName = nil
print("Author is \(authorName!)")

上面的代码会产生一个运行时错误

fatal error: unexpectedly found nil while unwrapping an Optional value

错误产生的原因就是我们企图对一个值为空的变量进行解包。更糟糕的是,这个错误在编译时是无法预见的,而是在运行时产生。

那么如何修改代码让其更安全呢?

我们可以检查下可选值的内容值

if authorName != nil {
  print("Author is \(authorName!)")
} else {
  print("No author.")
}

上面看似解决了问题,但是仍然不完美。如果我们依赖if判断,那么每次我们解包的时候都需要去做判断。如果某次忘了检查,程序还是会在运行时crash掉。

怎么解决这个问题呢,我们引入可选值绑定的概念。

Optional binding(可选值绑定)

Swift引入了一个新的特性:可选值绑定,使用这个新特性可以让我们更安全的访问可选值的内容。

if let unwrappedAuthorName = authorName {
  print("Author is \(unwrappedAuthorName)")
} else {
  print("No author.")
}

可选值绑定没有添加新的符号来进行绑定,而是使用了if let表达式来进行绑定,如果可选值内容不为空,可选值就会被解包,然后if判断就会正常执行上半部分,如果为空,那么else部分将会执行。

这样我们就避免使用强制解包带来的问题,强制解包只在我们确定可选值有内容时再使用。

因为if let命名比较烦,所以推荐let后的常量名和可选值名称相同(就像可选值的影子一样):

if let authorName = authorName {
  print("Author is \(authorName)")
} else {
  print("No author.")
}

我们甚至可以同时对多个可选值进行解包:

if let authorName = authorName,
   let authorAge = authorAge {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age.")
}

上面的代码只有在if条件的两个可选值都有值时才会执行。

更甚者,我们可以将解包操作和bool值来一起进行判断:

if let authorName = authorName,
   let authorAge = authorAge,
   authorAge >= 40 {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age or age less than 40.")
}

guard关键字

if let解包已经很好使了,但是有时候我们需要优先抛弃错误的情况,或者说有时候if let的判断语句太长,那么我们可以使用guard语法糖来简化操作。现实中这种情况应该很常见。

func calculateNumberOfSides(shape: String) -> Int? {
  switch shape {
  case "Triangle":
    return 3
  case "Square":
    return 4
  case "Rectangle":
    return 4
  case "Pentagon":
    return 5
  case "Hexagon":
    return 6
  default:
    return nil
  }
}

上面是一个多变形边数判断函数,我们调用它

func maybePrintSides(shape: String) {
  let sides = calculateNumberOfSides(shape: shape)

  if let sides = sides {
    print("A \(shape) has \(sides) sides.")
  } else {
    print("I don't know the number of sides for \(shape).")
  }
}

使用if let解包没有任何问题。但是,我们有时逻辑可能不是这样的

func maybePrintSides(shape: String) {
  guard let sides = calculateNumberOfSides(shape: shape) else {
    print("I don't know the number of sides for \(shape).")
    return
  }

  print("A \(shape) has \(sides) sides.")
}

我们优先排除了识别不了的图形的情况,这样做是不是更有可读性。

  • 语法格式:guard + 条件 + else + 代码块
    如果条件不成立,代码块就会执行,值得注意的是代码块必须要有return,否则编译器会报错。

Nil coalescing(空值合并)

swift有一种更好用的方式去解包可选值。不管可选值有没有值,如果没有值就使用默认值,这种方式就是空值合并。

var optionalInt: Int? = 10
var mustHaveResult = optionalInt ?? 0

空值合并使用2个问号操作符来实现,如果optionalInt有值,mustHaveResult值就是10,如果optionalInt=nil,那么mustHaveResult的值就是默认值0。

上面的代码等效于下面:

var optionalInt: Int? = 10
var mustHaveResult: Int
if let unwrapped = optionalInt {
  mustHaveResult = unwrapped
} else {
  mustHaveResult = 0
}

总结

  • nil表示没有值,和OC中的nil代表zero不同。
  • 非可选值类型的变量和常量必须要有一个非空(non-nil)的值。
  • 可选值类型的变量和常量就像一个盒子,里面可以包含值也可以什么都不填充。
  • 如果想要使用可选值类型的内部值,必须要先解包。
  • 解包可选值最安全的方式就是使用可选值绑定或者空值合并,只有在确定可选值包含内容时才可以使用强制解包

客官,路过左下角小❤️❤️点下,谢谢啊:-D

相关文章

网友评论

    本文标题:Swift 4 Optionals 可选值

    本文链接:https://www.haomeiwen.com/subject/qduesftx.html