swift学习笔记 ④ —— 可选类型

Swift学习笔记 - 文集

语法篇

一、可选类型 Optional

Swift 中,常量和变量是不允许赋予 nil 的。所以提供了可选类型用来处理 nil 值的问题。

var optionalInt: Optional

通过在类型名称后面加上后缀 ? 作为可选类型的简写,上面的代码等价于:

var optionalInt: Int?

如果在声明可选类型时没有初始值,那么这个可选类型的初试值就是 nil

例如如果想从一个数组通过下标取值时,考虑到提供的下标值可能存在越界的情况,那么我们就可以将返回值类型设为可选类型,以免出现数组越界导致错误的问题:

var array = [1, 15, 40, 29]
func get(_ index: Int) -> Int? {
    if index < 0 || index >= array.count {
        return nil
    }
    return array[index]
}
print(get(1)) // 打印结果为 Optional(15)
print(get(4)) // 打印结果为 nil
print(get(-1)) // 打印结果为 nil

通过打印结果可以看到,返回值为 Optional类型,里面存储这 15 ,并非以前直接将 15 返回。

需要注意的是,我们在项目中使用字典时,通过 key - value 形式获取字典中的值时,接收值的变量就是可选类型。这是因为字典中存在 valuenil 的情况。例如:

var dict = ["age" : 10]
var age = dict["age"]

例子中的变量 age 就是 Int?类型。

强制解包

可选类型是对其他类型的数据的一层包装,可以理解成一个盒子,里面装着被包装的数据,如果为 nil ,那就是一个空盒子。

如果想从盒子中取出对应的值,我们就要用 ! 进行强制解包。例如我们声明一个 age 可选类型的变量,如果我们直接对 age 跟另外一个 Int 数据进行加减运算是不可以的,程序会报错,这时我们就要用 ! 进行强制解包后再做运算:

var age: Int? = 10
var num = age + 20 // 这句代码会报错,因为 age 和 20 不是相同类型
// 正确做法,先强制解包,再运算
var add = age! + 20 // add = 30

注意:如果对值为 nil 的可选类型进行强制解包,会产生运行时错误

我们在给 Int 类型常量或者变量赋值时,也可以通过字符串赋值,例如:

let num = Int("123")

注意,此时的 num 并不是 Int 类型了,而是 Int? 类型。这是因为我们用字符串给 Int 类型赋值时,可能存在转换失败的情况,例如赋值 a123 时就会失败,那么编译器此时就会将 nil 赋值给 Int 类型。所以编译器将 num 设置为 Int? 可选类型。

所以我们在取值时,也要进行判断, num 的不为 nil ,就要进行强制解包获取:

if num != nil {
    print(num!)
}else {
    print("num is nil")
}

我们再来看下面一个例子:

if let first = Int("4") {
    if let second = Int("53") {
        if first < second && second < 100 {
            print("\(first) < \(second) < 100")
        }
    }
}

例子中,首先判断可选项 firstsecond是否转换成功,如果转换成功后再比较两个值并且second的值是否小于 100 。由于判断条件中设计可选项绑定,所以判断条件就不能再使用 && 符号,要用 , 代替。所以上面的代码就等价于:

if let first = Int("4"),
    let second = Int("53"),
    first < second && second < 100 {
    print("\(first) < \(second) < 100")
}

隐式解包

我们可以在声明可选变量时使用感叹号 ! 替换问号 ? 。这样可选变量在使用时就不需要再加一个感叹号 ! 来获取值,它会自动解析。

let num1: Int! = 10
let num2: Int = num1 // 10

我们用 Int! 声明了可选变量 num1 ,然后将 num1 的值赋给 num2 ,这是我们就不用在进行强制解包,因为编译器会自动解析,将 num1 的值赋给 num2

需要注意的是,如果我们给例子中的可选变量 num1 赋值为 nil 的话,那么系统就会报错,因为用 Int! 声明了可选变量 num1 ,编译器会自动解析,对一个为 nil 的可选类型进行解包的话,自然会报错。

二、可选绑定

使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 ifwhile 语句中来对可选类型的值进行判断并把值赋给一个常量或者变量。

while循环中使用可选项绑定

首先我们来看这样一个需求:给定一个数组,遍历数组要求将数组中遇到的正数加起来,如果遇到负数或者非数字,那么就停止遍历。

我们直接来看一下具体实现代码:

var strs = ["10", "20", "abs", "-20", "40"]

var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
    sum += num
    index += 1
}
print(sum) // 打印 30

根据需求,我们声明可选类型的 num 常量,然后遍历数组将值赋给 num ,并且判断 num 的值。如果转换失败或者 num 的值小于 0 ,也就是遇到负数或者非数字就退出循环。

三、空合并运算符 ??

Swift 中支持空合并运算符 ?? ,也有人称空合并运算符为二目运算,主要用来比较两个操作参数的值然后赋值,例如 a ?? b 。有下面几条规则:

  • a 必须是可选项,b 是不是可选项都可以
  • a 和 b存储的类型必须相同。例如 a = Int?,那么 b要么是 Int 型的可选项,要么就是 Int 类型
  • 如果 a 为 nil,就返回 b,如果a 不为 nil ,就返回 a
  • 如果 b 不是可选项,那么返回 a 的时候就会自动解包

下面来看几个例子帮助理解:
a 不为 nil,则返回 a,c = Optional(1) :

let a: Int? = 1
let b: Int? = 2
let c = a ?? b // c 是 Int?,Optional(1)

a 为 nil,则返回 b,c = Optional(2) :

let a: Int? = nil
let b: Int? = 2
let c = a ?? b // c 是 Int?,Optional(2)

a 为 nil,则返回 b,c = nil :

let a: Int? = nil
let b: Int? = nil
let c = a ?? b // c 是 Int?,nil

a 不为 nil,因为b为 Int 类型,则返回 a! ,c = 1 :

let a: Int? = 1
let b: Int = 2
let c = a ?? b // c 是 Int,1

a 为 nil,则返回 b ,c = 2 :

let a: Int? = nil
let b: Int = 2
let c = a ?? b // c 是 Int,2

经过上面几个例子分析可以发现,空合并运算符的返回值类型其实取决于 b 的类型,如果 b 为 Int类型,则返回值为 Int类型,如果 b 为 Int?类型,则返回值为 Int?类型。

多个空合并运算符一起使用

当多个空合并运算符一起使用时也很简单,就是逐步从左往右计算即可:

let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3 // c 是Int , 1

例子中,先计算 a ?? b ,返回a,再进行 a ?? 3 ,很显然结果就是 c = 1。

字符串插值

在字符串插值或者直接打印可选类型时,下面的代码,编译器会报出警告:

var age: Int? = 10
print("age is \(age)")

那么具体如何消除呢?实际有3中方法来消除警告:

  • 使用强制解包
print("age is \(age!)")
  • 使用 String 的一个初始化方法 describing:
print("age is \(String(describing: age))")
  • 使用空合并运算符
print("age is \(age ?? 0)")

四、多重可选类型

我们来看这样一段代码:

var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10

print(num2 == num3)

我们知道,num1 是可选类型,里面存储了 10 ,具体结构就是num1 是一个 Int? 的盒子,里面存储了 Int 类型的10。那么 num2num3 的结构是什么样的呢?我们用简单的示意图来展示一下:

swift学习笔记 ④ —— 可选类型_第1张图片
示意图.png

num2num3 实际上是两层盒子,最外层是 Int?? ,里面又有一个 Int? 的盒子,里面存储了 Int 类型的10。所以 num2 == num3 的打印自然也就是 true

我们可以通过 lldb的一个指令,来查看数据的内存结构:

frame variable -R // 可以简写成 fr v -R
swift学习笔记 ④ —— 可选类型_第2张图片
数据结构.png

通过调试指令可以清楚的看到数据结构。

更多技术知识请扫码关注微信公众号

iOS进阶

swift学习笔记 ④ —— 可选类型_第3张图片
iOS进阶.jpg

你可能感兴趣的:(swift学习笔记 ④ —— 可选类型)