语法篇
一、可选类型 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
形式获取字典中的值时,接收值的变量就是可选类型。这是因为字典中存在 value
为 nil
的情况。例如:
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")
}
}
}
例子中,首先判断可选项 first
和 second
是否转换成功,如果转换成功后再比较两个值并且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)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if
和 while
语句中来对可选类型的值进行判断并把值赋给一个常量或者变量。
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。那么 num2
和 num3
的结构是什么样的呢?我们用简单的示意图来展示一下:
num2
和 num3
实际上是两层盒子,最外层是 Int??
,里面又有一个 Int?
的盒子,里面存储了 Int
类型的10。所以 num2 == num3
的打印自然也就是 true
。
我们可以通过 lldb
的一个指令,来查看数据的内存结构:
frame variable -R // 可以简写成 fr v -R
数据结构.png
通过调试指令可以清楚的看到数据结构。
更多技术知识请扫码关注微信公众号
iOS进阶
iOS进阶.jpg
网友评论