可选值

在编程世界中有一种非常通用的模式,那就是某个操作是否要返回一个有效值。
在swift开发中,nil也是一个特殊的类型.因为和真实的类型不匹配是不能赋值的(swift是强类型语言)
但是开发中赋值nil,在所难免.因此推出了可选类型
可选类型的取值:有值、空值
可选值的目的:让代码更严谨

可选值概览

1、 定义

1
2
3
4
5
6
7
8
9
10
11
// 定义可选值
var name :String?
name = "科比"
// 1、可选值解包
if name != nil {
print(name!) //科比
}
// 2、可选绑定
if let str = name {
print(str) //科比
}

2、应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将字符串类型转成Int类型
let str = "123"
let result : Int? = Int(str) // nil/Int
print(result) //Optional(123)
// 根据文件名称,读取路径
let path : String? = Bundle.main.path(forResource: "123.plist", ofType: nil)
print(path) // nil
// 从字典中取内容
let dict : [String:Any] = ["name":"阿呆","age":18]
print(dict["name"]) //Optional("阿呆")
print(dict["age"]) //Optional(18)

3、if let
当遇到 nil 时终止的循环
使用if let来进行可选绑定(optional binding)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var array = ["one", "two", "three", "four"]
if let idx = array.index(of: "four") {
array.remove(at: idx)
}
print(array) //["one", "two", "three"]
// if 的可选绑定也可以跟一个布尔限定语句搭配
if let idx = array.index(of: "two" ), idx != array.startIndex {
array.remove(at: idx)
}
print(array) //["one", "three"]
// 多个 let 的每一部分也能拥有一个布尔值限定的语句
let urlString = "http://www.objc.io/logo.png"
if let url = URL(string: urlString ), url.pathExtension == "png",
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
{
let view = UIImageView(image: image)
}

4、while let
当遇到 nil 时终止的循环

1
2
3
4
5
6
7
while let line = readLine(), !line.isEmpty{
print ( line )
}
for i in 0..<10 where i % 2 == 0{
print(i)
}

nil 合并运算符

?? 操作符连接的使用默认值的语句,它代表 “这是一个默认值”
1、基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let str = "wda"
let num = Int(str) ?? 0
print(num) // 0
// 数组的 first和last 返回的是可选值
let array = [1,2,3]
let a = array.first ?? 0
print(a) // 1
// 通过数组下标取值返回的不是可选值
array.count > 5 ? array[5] : 0 // 0
// 这时可以给Array写扩展实现
extension Array {
subscript(safe idx: Int ) -> Element? {
return idx < endIndex ? self[idx] : nil
}
}
// 调用扩展
array[safe: 5] ?? 0 // 0

2、合并操作也能够进行链接(有多个可选值,并且想要按照顺序选取其中非 nil 的值)

1
2
3
4
5
6
let i: Int? = nil
let j: Int? = nil
let k: Int? = 42
let m = i ?? j ?? k // 按顺序取,取第一个非nil值
print(m) // Optional(42)
print(type(of: m)) //Optional<Int>

3、??和if let的配合使用(可看作是和“or”语句类似)

1
2
3
if let n = i ?? k { // 和 if i != nil || k != nil 类似
print (n) // 42
}

4、而多个if let语句并列则等价于 “and”

1
2
3
if let _ = i , let _ = j { // 和if i != nil && j != nil 类似
print("啦啦啦")
}

5、双重嵌套的可选值
因为可选值是链接的,如果你要处理的是双重嵌套的可选值,并且想要使用 ?? 操作符的话,你 需要特别小心 a ?? b ?? c 和 (a ?? b) ?? c 的区别。前者是合并操作的链接,而后者是先解包 括号内的内容,然后再处理外层:

1
2
3
4
let s1: String?? = nil // nil
(s1 ?? "inner") ?? "outer" // inner
let s2: String?? = .some(nil) // Optional(nil)
(s2 ?? "inner") ?? "outer" // outer

可选值中的map和flatMap

1、可选值 map
可选值的 map 方法只会操作一个值,那就是该可选值中的那个可能的值,这一点与序列中的map有所不同

1
2
3
4
//将字符数组汇总第一个字符转换为字符串
let characters: [Character] = ["a", "b", "c"]
let rstChar = characters.first.map { String($0) }
print(rstChar) // Optional("a")

2、可选值 flatMap (展平可选值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let stringNumbers = ["1", "2", "3", "foo"]
let x = stringNumbers.first.map { Int($0) }
print(x)// Optional(Optional(1))
//map 返回可选值 (first 可能会是 nil),Int(String) 也返回可选值 (字符串可能不是一个整数),最后 x 的结果将会是 Int??
// 这时可以使用flatMap把结果展平为单个可选值
let y = stringNumbers.first.flatMap { Int($0)}
print(y) //Optional(1)
//这样 y 就是 Int? 类型
// flatMap 和 if let 非常相似
if let a = stringNumbers.first, let b = Int(a) {
print (b) // 1
}

3、使用 map 和 flatMap 来重写多个 if let 的语句

1
2
3
4
5
6
7
8
let urlString = "http://www.objc.io/logo.png"
let view = URL(string: urlString)
.flatMap { try? Data(contentsOf: $0) }
.flatMap { UIImage(data: $0) }
.map { UIImageView(image: $0) }
if let view = view {
self.liveView = view
}

4、使用 flatMap 过滤 nil

1
2
3
4
let numbers = ["1", "2", "3", "foo"]
let reuslt = numbers.flatMap { Int($0) }.reduce(0, +)
print(reuslt) // 6
//这里的flatMap的作用:为可选值的序列进行展平

强制解包的时机

当你能确定你的某个值不可能是 nil 时可以使用叹号
其实你也可以使用source.flatMap { $0 }这个方法替代 !

改进强制解包的错误信息

1
2
3
4
5
6
7
8
9
infix operator !!
func !! <T>(wrapped: T?, failureText:@autoclosure() -> String) -> T {
if let x = wrapped { return x }
fatalError(failureText())
}
let s = "foo"
let w = Int(s) !! "Expecting integer, got \"\(s)\""
print(w) //fatal error: Expecting integer, got "foo": file MyText.playground, line 10

在调试版本中进行断言

在调试时触发断言,但是在发布版本中打印 0

1
2
3
4
5
6
7
8
9
10
11
let s = "rt"
infix operator !?
func !?<T: ExpressibleByIntegerLiteral>
(wrapped: T?, failureText: @autoclosure () -> String) -> T
{
assert(wrapped != nil, failureText ())
return wrapped ?? 0
}
let i = Int(s) !? "Expecting integer, got \"\(s)\""
print(i) //assertion failed: Expecting integer, got "rt": file MyText.playground, line 22

打赏支持一下呗!