可选型 就像是 薛定谔的猫
前言
作为一个Swifter 的初学者,弄清楚 可选型(Optional) 至关重要
我们来看一下 官方对 Optional 的定义:
A type that represents either a wrapped value or nil, the absence of a value.
public enum Optional : ExpressibleByNilLiteral {
case none
/// The presence of a value, stored as `Wrapped`.
/// 以包装形式存在的值
case some(Wrapped)
...
}
也就是说 Optional 是 一种含有2个类型的枚举,它要么为空,要么有值
有值: var statusCode: Int? = 404
空值: var errorCode: Int? = nil
等价于
var errorCode: Int? (默认可选型 值为nil)
类比
当我们在代码里声明一个可选型的类型时,用 ?
来执行
let number: Int? = 10 // number 此时就是可选型
当我们想要用到 number 这个值的时候,我们去试着打印它得到 : Optional(10)
当我们用 number
去与同类型的值相加时 let a = number + 10
,此时就会得到一个编译时的错误
也就是说,可选型 Optional(10) 不能直接参与运算。
强制解包 !
在 可选型 的后面通过增加!
的形式, 解开被?
包装的类型 number
let a = number! + 10
# 得到a 的类型是 Int,而不是Int?,就代表此时a 类型明确 是Int
那么反观 number ,当我们打印的时候发现,number 还是 Optional(10)
# 意味着 强制解包 并不会影响 原来被包装的类型
如果 let number: Int? = nil 的话,则强制解包 number! 会崩溃
我们来类比一下薛定谔的猫
一只猫在箱子里,存在着一种既死又活的状态,直到打开的时候我们才知道结果
薛定谔的猫 | 可选型 | let number: Int? = 10 |
---|---|---|
猫 | 包装值的类型 | Int? |
箱子 | 包装值存放的盒子 | Int? 存放的地方 |
? | 套猫的绳子 | ? |
! | 开盒子的钥匙 | ! |
在这里, 我们把 死活状态 类比为猫的 存在与否
? 比作套索 ,我们用套索去盒子里圈喵星人的时候,我们并不知道盒子里有没有喵星人
! 比作开盒子的钥匙,开不开就看你控不控制的住了
撸er 们 ,不斩无名之辈,不开无猫之门
但实际上,没打开盒子的时候,最多存在着2种情况
1. Int? 的值为 nil 喵星人不在
2. Int? 的值为 10 喵星人活蹦乱跳,ok 撸它
我们不能贸然的用钥匙
去开门
你撸猫的动作都准备好了
发现没有猫, 你难不难受?
你能想象你在输入 一大串密码后 查看开你的银行卡余额
发现一毛没有
你会怎么样?
你会崩溃啊,系统也会崩溃啊
但如果当我们确定有喵星人的时候,我们是可以开门把喵星人揪出来的,
虽然我们把猫从盒子里拿出来了,但是盒子又恢复了初始状态,里面还是有2种可能
结论:
1.当我们 不确定可选型 有没有值 的时候,最好是不要去 强制解包
2.可选型有值,用!
去取值,反之不取
3.强制解包 并不影响 被解包的类型,依旧还是可选类型
可选绑定 if let
解包除了强制解包这种粗鲁的行为外,还有可选绑定
if let value = someOptional {
// do next
// 就是说如果 someOptional 有值,才会进 if 循环
// value 是 someOptional强制解包后的值,类型是确定的
// value 的作用域 仅限于 {} 内
// 可以是 if let / if var
}
举个例子:
let number = Int("777")
// 这里Int("777") 也是Int 类型 初始化的一种
// 返回的是一个可选型
// 因为如果传字符串 "zzz" 就无法转为Int,结果是不确定的
if let a = number {
print("转Int 成功",a) // 此时的a 是 number 解包后的值 777,而不是 Optional(777)
} else {
print("转Int 失败 ")
}
* 如果有多个条件的可选值需要共同成立,比如
if let a = Int("1"),
let b = Int("2") {
print(a + b)
}
// 省略一个if ,中间以 逗号 拼接
if let 配合 while 使用
// 计算数组内 为正数的值 的总数
var array = ["10","10","-10","a"]
var index = 0
var sum = 0
while let s1 = Int(array[index]),s1 > 0 {
sum += s1
index += 1
}
print(sum)
guard let
如果说 if let
是 走到最后
,那么 guard let
就代表着 提前退出
guard 相比 if 来说,用起来更加舒服
guard let value = someOptional else {
// value 没有值,才会进 else
// value 的作用域 不限于{}
// 可以是 guard let / guard var
// must do return
// guard 必须要返回 退出 当前作用域
}
print(value) // 这里的value 是 someOptional 解包后的值
举个例子:
let number = Int("777")
guard let num1 = number else {
print("转换失败")
return
}
print(num1 + 10)
// 能来到这里就说明 num1 是有值的,类型是确定的
// 打印 787
guard 相较于 if
- 层次比较分明
- 变量作用域更加广泛
- 提前退出,效率更高
空合并 运算符 ??
系统的定义是这样的:
func ?? (optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
举个例子:
表达式 a ?? b // 它意味着:如果a 不为空,返回a,否则返回b
- 这里有一些注意点:
1. a 必须是可选型,从定义中可以看到第一个参数.. optional: T? , 就证明了a 必须是可选型
2. a 和 b 的类型 除了nil 这个类型以外,另外一个类型必须是一致的
比如:
let a: Int? = 10
let b: Int? = 20
let c = a ?? b
// c = Optional(10)
// a 和 b 都是可选型,另外一个类型都是 Int
3.b 可以是可选型,也可以不是
比如:
let a: Int? = 10
let b: Int = 20
let c = a ?? b
// c = 10
4.如果a 不为nil,返回a,此时系统并不会 牵扯到 b,b是多余的.
# 这里我们 注意 defaultValue: @autoclosure () 第二个参数 是带有自动闭包的
# 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行
5.如果a 不为nil ,但b 不是可选型,返回a 的时候 a会自动解包
什么意思呢?比如
let a: Int? = 10
let b: Int = 20
let c = a ?? b
// a 是可选值 不为nil ,b 是 Int,非可选型
// 此时c = a! ,因为 ?? 后面的 b不是可选型,所以c 自动解包
- 结论
其实从官方定义可以看出,.... T?) rethrows -> T?
?? 的结果返回什么类型,取决于 参数 b 的类型
隐式解包
有时候,在特定的情况下,一个 Optional 可以被确保永远都有值 (或者说理应永远都有值)
这时候每次使用都进行判空和解包,就显得很多余
在 可选型 初始化的时候 后面直接加 !,在编译的时候就 让其自动解包
你会觉得这 提莫 不就是 另一种形式 的强制解包吗?
强制解包可选型
var aString: String? = "ttt"
aString! // 强制解包 "ttt"
隐式解包可选型
var aString: String! = "ttt" // 隐式解包
* 依旧可以置nil,因为aString 依旧是可选型
aString! // 强制解包 "ttt"
aString // Optional("ttt")
aString = nil
* 如果将aString 赋值给bString,则 bString不需要解包了
let bString: String = aString
bString = "ttt"
那么问题来了
1.如果我知道盒子里有喵星人,我还用套索 费劲的 去抓它吗?换言之 我还需要用可选型修饰吗?
2.为什么不直接用钥匙打开盒子的门呢?为什么不直接定义为String类型呢?
3.我如果确定它一直有值,从某种意义上说,那它不就丢失了可选型的初衷吗?
我的理解,不知道对不对,还请指正
xib 中的控件我们都应该清楚,当我们拖拽出一个button 的时候 ,会发现:
@IBOutlet weak var btn: UIButton!
这个btn 就是 隐式解包,对吧,就是说用到这个btn 的时候,自动解包拿到btn
-
这个地方为什么要
隐式解包
呢?我认为:- 每个属性初始化的时候都是要有值的,或者是可选的,或者隐式解包的
- 拖拽出来的btn 在 awakeFormNib 初始化结束之后 才会被设置属性
- 初始化之后 大概率是要用的吧,不然也不会去拖线
- 也就是说btn 肯定是会存在的,但是要在初始化之后,除非你把连的线砍了
-
那为什么不用
?
修饰呢- 大概率是因为用?每次都要 强制解包很麻烦,所以这里的属性用!
结论:
隐式解包不是最安全的做法,慎用
参考:When Should You Use Implicitly Unwrapped Optionals
字符串插值
有时候我们在打印一个字符串的时候,有可选型的插值,则会给出警告
var age: Int? = 10
# 三个fix 对应下面3中解决方法
print("zzz is \(age!)") // 强制解包
print("zzz is \(String(describing: age))") // String 初始化
print("zzz is \(age ?? 20)") // 空合并给默认值
后续有新的继续补充...
个人笔记 有问题请多多指教
thanks for reading
....