Swift中加入了可选类型Optional
,用来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 你可以解析并访问这个值, 或者根本没有值;
可选类型的作用
- 处理哨岗值问题
Optional
的存在是很有必要的,大部分语言是没有这种类型的,它们表示值可能缺失的情况就不太方便了;比如说一个Int类型的属性,如何表示这个属性未赋值过或者说缺失为空的情况呢;Int这类基本类型并不能像其他对象一样通过nil(null)来表示缺失情况,同样也不好使用默认值0来表示,那么一般的处理方式就是设置一个哨岗值
来表示:我们可以约定这个Int类型的属性默认/缺失值为-1,这个-1就是哨岗值;OC中的NSNotFound
其实也是类似的哨岗值,只不过这个值是个比较大的数;
static const NSInteger NSNotFound = NSIntegerMax;
#define NSIntegerMax LONG_MAX
不过这种策略是有问题的,因为这个哨岗值不管从哪个角度看都是一个真实值;你也可能会忘记检查这个值而不小心使用了它;而可选类型很好的处理了这种情况;
- 类型安全
在OC中,对nil发送消息是安全的,但在Swift中是有问题的;编写代码时,开发者都不能明确某个对象在某个时刻会变为nil;如果再去使用这个对象,就可能会造成异常;有了可选类型,开发者在使用对象时就必须去解析这个值并判断为nil的情况;
可选类型的本质
Optional结构如下:
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
....
}
- 可以看到,它本质是一个
enum
类型,而且是一个关联值的enum;这个枚举只有两个值,表示缺少值的none,表示具体关联值的some(Wrapped),Wrapped就是具体值;
获取关联值的唯一方法就是使用switch
或者if case
语句;和哨岗值不同,除非通过解包取出关联值,否则是不可能意外的得到这个值; - 另外
Optional<Wrapped>
这里也是一个泛型,原则上任何类型都有可选类型; - Optional 同时也遵循了
ExpressibleByNilLiteral
协议,ExpressibleByNilLiteral这个协议就是nil字面量协议,类似的还有字符串的字面量协议ExpressibleByStringLiteral
,数组字面量协议ExpressibleByArrayLiteral
;这也是case none
就能以字面量nil
表示的原因;
我们可以这样声明一个可选整型:
var opValue: Optional<Int> = Optional.init(1)
// or
// var opValue: Optional<Int> = 1
这个声明可以用?
语法糖简化表示:
var opValue: Int? = 1
使用switch解包得到关联值:
switch opValue {
case .none:
print("nil")
case .some(let value):
print(value)
}
或使用if case
语句:
if case Optional.some(let value) = opValue {
print(value)
}else {
print("nil")
}
因为遵循了字面量协议上面的解包可以替换为:
switch opValue {
case nil:
print("nil")
case .some(let value):
print(value)
}
Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
强制解析
可以使用 if 语句和 nil 比较来判断一个可选值是否包含值;
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号!
来获取值。这个惊叹号表示“我知道这个可选有值,请使用它”,这就是可选值的强制解析;
if opValue != nil {
print(opValue!)
}else {
print("nil")
}
使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值。
可选绑定
- if let
可选类型很常用,很多时候都需要使用上面的switch,if case解析;这种解析的确有点冗余;不过使用if let
进行可选绑定(optional binding
)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量,可以更简单的解析;
上面例子使用可选绑定的代码:
if let value = opValue {
print(value)
}
也可以在同一个if语句中绑定多个值,当多个可选值都有解析值时条件成立:
只有opValue1,opValue2都有值时才会调用输出结果:
let opValue1: Int? = 1
let opValue2: Int? = nil
if let v1 = opValue1, let v2 = opValue2 {
print(v1,v2)
}
- guard let
通常使用if let判断有值之后,会做具体的逻辑实现,通常代码多,而且也多了一层分支;guard let
和if let刚好相反,guard let守护一定有值。如果没有,直接返回;guard let 在一定程度上能使代码更加清晰易读:
guard let v = opValue else { return }
print(v)
一个比较明显的例子:
if let v1 = opValue1 {
if let v2 = opValue2 {
if let v3 = opValue3 {
print(v1,v2,v3)
}
}
}
guard let v1 = opValue1 else { return }
guard let v2 = opValue2 else { return }
guard let v3 = opValue3 else { return }
print(v1,v2,v3)
guard let同样可以绑定多个值:
guard let v1 = opValue1,let v2 = opValue2,let v3 = opValue3 else { return }
print(v1,v2,v3)
在
if
条件语句中使用常量和变量来创建一个可选绑定,仅在if
语句的句中(body
)中才能获取到值。相反,在guard
语句中使用常量和变量来创建一个可选绑定,仅在guard
语句外且在语句后才能获取到值;
另外,guard并不局限于绑定上,它也能够接受任何在if语句中能接受的条件;附:使用 guard 的正确姿势
可选链
可选链Optional Chaining
:多个连续的调用可以被链接在一起形成一个调用链,如果其中任何一个节点为空(nil)将导致整个链调用失败。也就是说可以将可选值的调用链接起来;
let str: String? = ""
str?.uppercased().lowercased()
- 在OC中,对nil发送消息什么都不会发生;在Swift中,可以通过可选链来达到同样的效果;
delegate?.callback()
和OC不同的是,如果没有值,那么这里的问号对于代码的阅读者来说是一个清晰的信号,表示方法可能不会调用;
- 当调用可选链得到一个返回值时,这个返回值也是可选类型;因为可选链的结果可能为nil;
- 可以使用可选链式调用代替强制解析,使用可选链当可选值为空时调用只会调用失败,然而强制解析将会触发运行时错误。
- 可选链简化判断:
var str: String? = ""
if str != nil {
str = str?.lowercased()
if str != nil {
str = str?.uppercased()
print(str)
}
}
if let result = str?.lowercased().uppercased() {
print(result)
}
隐式解析
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。这种情况我们就可以定义隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。
let opValue: Int! = 1
print(opValue + 1)
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
网友评论