Swift-可选值

Posted by zhangsihuai on 2018-04-01

可选值

Optional是Swift中的一种枚举类型,用来表达某个类型的可能值。

1
2
3
4
enum Optional<Wrapped> {
case none
case some(Wrapped)
}

表示方式

  • 可选值遵守了ExpressibleByNilLiteral协议,因此可以用nil来替代.none
  • 而非可选值可以直接表示成可选值
  • return idx 等价于 return .some(idx)
  • 可选值类型的表示可以在类型的后面加上?进行表示,如Int?表示Optional<Int>类型

解包

  • 要使用可选值中实际的值的话需要进行解包操作
  • 因为可选值是枚举类型,所以可以使用switch语法来取出可选值中的值
1
2
3
4
5
6
7
let array = [1, 2, 3]
switch array.first {
case .some(let first):
print(first) // 输出 1
case .none:
break
}
1
2
3
4
5
6
switch array.first {
case let first?:
print(first) // 输出 1
case nil:
break
}
可选绑定
  • 使用switch的方式太过繁琐
  • 使用if let语法来进行可选绑定optional binding,进行解包操作,并且可以和布尔语句进行搭配
1
2
3
if let first = array.first, first > 0 {
print(first)
}
  • 可以在if语句中绑定多个值,并且后面的绑定值可以依据前面成功的解包值来进行操作
1
2
3
4
5
6
7
8
let viewControllers: [String: UIViewController] = ["Home": HomeViewController]
if let vc = viewControllers["Home"],
let homeVC = vc as? HomeViewController {
// do something
}
```

* `while let`语句和`if let`类似,`while`语句在当前可选值为`nil`的时候停止循环

let array: [Int?] = [1, 2, 3, nil]
let iterator = array.makeIterator() // 迭代器返回最后一个元素后会返回nil
while let value = iterator.next() {
print(value) // 1 2 3
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#### 双重可选值
* 一个可选值本身可以被当作值被另一个可选值包装,导致可选值嵌套在可选值中
* `Optional<Optional<Int>>` = `Int??`

```swift4.0
let array: [Int??] = [1, 2, 3, nil]
for a in array {
print(a)
}
输出:
Optional(Optional(1))
Optional(Optional(2))
Optional(Optional(3))
nil

  • 可以使用case做模式匹配,”脱去”最外层的可选值,nil元素也被过滤掉了
  • case let a?case .some(let a)的一种写法
1
2
3
4
5
6
7
8
let array: [Int??] = [1, 2, 3, nil]
for case let a? in array {
print(a)
}
输出:
Optional(1)
Optional(2)
Optional(3)

可选链

  • Swift中没办法向一个nil对象发送消息,但可选链可以实现一样的效果
1
2
let str: String? = "abc"
let upperStr = str?.uppercased() // Optional<"ABC">
1
2
var str: String?
let upperStr = str?.uppercased() // nil
  • 可选链的执行结果都是可选值,str不为nil的时候,可以得到执行uppercased饭后之后的值。若strnil?后面的方法都不会被调用。因为得到的结果需要考虑str是否为nil的情况,所以将值设置为可选值。
  • 可选链是一个展平操作
1
2
3
let str: String? = "abc"
let upperStr = str?.uppercased().lowercased()
// 结果是 Optional("ABC") 而不是 Optional(Optional("abc"))
  • 可选链对于下标和函数调用也适用
1
2
3
4
5
6
var closure: ((Int) -> ())?
closure?(1) // closure 为 nil 不执行

let dict = ["one": 1, "two": 2]
dict?["one"] // Optional(1)
dict?["three"] // nil
  • 通过可选链进行赋值,先判断当前变量是否为nil,不为nil的进行赋值
1
2
3
4
5
6
7
var a: Int? = 0
a? = 1
a // Optional(1)

var b: Int?
b? = 1
b // nil

与可选值有关的高阶函数

1. map

  • 这个方法接受一个闭包,如果可选值有内容则调用这个闭包进行转换
1
2
3
var dict = ["one": "1", "two": "2"]
let result = dict["one"].map{ Int($0) }
// Optional(Optional(1))
  • 上面的代码中我们从字典中取出字符串”1”,并将其转换为Int类型,但因为String转换成Int不一定能成功,所以返回的是Int?类型,而且字典通过键不一定能取得到值,所以map返回的也是一个Optional,所以最后上述代码result的类型为Int??类型。
  • 要解决这种问题,需要一个展平操作

2. flatMap

  • flatMap可以把结果展平成为单个可选值
1
2
3
var dict = ["one": "1", "two": "2"]
let result = dict["one"].flatMap{ Int($0) }
// Optional(1)
  • 注意,这个方法是作用在Optioanl的方法,而不是作用在Sequence上的
  • 作用在Sequence上的flatMap方法在Swift4.1中被更名为compactMap,该方法可以将序列中的nil过滤出去
1
2
3
4
5
let array = ["1", "2", "3", nil]
let result = array.compactMap{ $0 } // ["1", "2", "3"]

let array = ["1", "2", "3", "four"]
let result = array.compactMap{ Int($0) } // [1, 2, 3]

可选值与Equatable

  • 可选值有==操作符,可以用来进行可选值与非可选值之间的比较,前提是可选值中的类型是可比较的
1
2
3
4
5
6
7
func ==<T: Equatable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case (nil, nil): return true
case let (x?, y?): return x == y
case (_?, nil), (nil, _?): return false
}
}
  • 但是,这样并不是说明可选值类型遵守了Equatable协议,可选值在Swift4之前是没有实现Equatable
1
2
3
let a = [1, 2, nil]
let b = [1, 2, nil]
a == b // 在Swift4之前无法进行判等,Swift4.1可以
  • Swift4.1进行的改进:

Swift 4.1’s Optional, Array and Dictionary now conform to Equatable and Hashable whenever their underlying values or elements conform to these protocols.