什么是可选值类型
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
网友评论