Swift 5 学习笔记 ④

1,229 阅读7分钟

语法篇

一、可选类型 Optional

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

var optionalInt: Optional<Int>

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

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 的结构是什么样的呢?我们用简单的示意图来展示一下:

示意图

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

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

frame variable -R // 可以简写成 fr v -R

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

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

iOS进阶

iOS进阶