从 可选型 到 Swift

可选型 就像是 薛定谔的猫


前言

作为一个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

  1. 层次比较分明
  2. 变量作用域更加广泛
  3. 提前退出,效率更高

空合并 运算符 ??

系统的定义是这样的:

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

字符串插值

从 可选型 到 Swift_第1张图片
image

有时候我们在打印一个字符串的时候,有可选型的插值,则会给出警告

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

....

你可能感兴趣的:(从 可选型 到 Swift)