本文主要分析Optional源码、Equatable+Comparable协议
Optional分析
swift中的可选类型(Optional)
,用于处理值缺失
的情况,有以下两种情况
有值,且等于x
没有值
这点可以通过swift-source->Optional.swift
源码(CMD+P
,搜索Optional)源码来印证
@frozen public enum Optional<Wrapped>: ExpressibleByNilLiteral { ...... //为nil case none ...... //有值 case some(Wrapped) ...... }
- 通过源码可知,
Optional
的本质是enum
,当前枚举接收一个泛型参数Wrapped
,当前Some的关联值
就是当前的Wrapper
,下面两种写法完全等价
var age: Int? = 10 等价于 var age1: Optional<Int> = Optional(5)
- 【Optional使用模式匹配】:既然
Optional
的本质是枚举,那么也可以使用模式匹配
来匹配对应的值,如下所示
//1、声明一个可选类型的变量 var age: Int? = 10 //2、通过模式匹配来匹配对应的值 switch age{ case nil: print("age 是个空值") case .some(let val): print("age的值是\(val)") } <!--或者这样写--> switch age{ case nil: print("age 是个空值") case .some(10): print("age的值是10") default: print("unKnow") }
【Optional解包】:因为是Optional
类型,当我们需要从其中拿到我们想要的值时,需要对其进行解包
,因为当前的可选项是对值做了一层包装的,有以下两种方式:
- 1、
强制解包
:好处是省事,坏处是一旦解包的值是nil,那么程序就会崩溃
- 2、通过
可选项绑定
:判断当前的可选项是否有值
if let
:如果有值,则会进入if流程guard let
:如果为nil,则会进入else流程
//3、可选项解包 var age: Int? = nil //3-1、强制解包 //如果age为nil,则程序崩溃 print(age!) //3-2、可选值绑定 <!--方式一--> if let age = age{ //如果age不为nil,则打印 print(age) } <!--方式二--> guard let tmp = age else { print("age为nil") return } print(tmp)
可选项绑定总结
- 1、使用
if let
创建的内容当中age仅仅只能在当前if分支的大括号内访问 - 2、使用
guard let
定义的tmp在当前大括号外部也是能访问的
Equatable协议
在上面的例子中,可以通过==判断两个可选项是否相等,原因是因为Optinal在底层遵循了Equatable
协议
var age: Int? = 10 var age1: Optional<Int> = Optional(5) age == age1
- 继续回到
Optional
源码中,可以看到Optional遵循了Equatable
协议
extension Optional: Equatable where Wrapped: Equatable { ...... @inlinable public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l == r case (nil, nil): return true default: return false } } }
swift标准库中的类型
在swift中的类型,可以通过遵循Equatable
协议来使用相等运算符(==)
和不等运算符(!=)
来比较两个值相等还是不相等
,Swift标准库中绝大多数类型都默认实现了Equatable
协议
例如下面的例子,对于Int类型来说,系统默认实现了 ==
var age2: Int = 20 var isEqual = age1 == age2 print(isEqual) <!--打印结果--> false
自定义类型
对于自定义的类型
,如果想实现 ==
,应该怎么办呢?
- 如果像下面这样写,是会直接报错的
- 可以通过
遵循Equatable协议实现
,如下所示
//2、自定义类型如何实现Equatable协议 struct CJLTeacher: Equatable{ var age: Int var name: String } var t = CJLTeacher(age: 18, name: "CJL") var t1 = CJLTeacher(age: 19, name: "CJL") print(t == t1) <!--打印结果--> false //如果将t1中的age改成18,打印结果是什么 true
为什么呢?其根本原因是因为遵守了Equatable
协议,系统默认帮我们实现了==方法
- 查看
SIL
方法,是否如我们猜想的一样?经过验证确实与我们猜测结论是一致的
查看__derived_struct_equals
方法的实现
疑问:如果是Class类型呢?
如果像Struct
那么写,会报错,提示需要自己实现Equatable
协议的方法
- class中手动实现
Equatable
协议的方法
//3、如果是class类型呢?需要手动实现Equatable协议的方法 class CJLTeacher: Equatable{ var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool { return lhs.age == rhs.age && lhs.name == rhs.name } } var t = CJLTeacher(age: 18, name: "CJL") var t1 = CJLTeacher(age: 19, name: "CJL") print(t == t1)
- 如果class中的age和name都是
Optional
呢?
//4、如果class中的属性都是可选类型呢?底层是调用Optional的==来判断 class CJLTeacher: Equatable{ var age: Int? var name: String? init(age: Int, name: String) { self.age = age self.name = name } static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool { return lhs.age == rhs.age && lhs.name == rhs.name } } var t = CJLTeacher(age: 18, name: "CJL") var t1 = CJLTeacher(age: 19, name: "CJL") print(t == t1)
查看其SIL文件可以验证这一点:底层是通过调用Optional的==来判断
区分 == vs ===
==
相当于 equal to,用于判断两个值是否相等
===
是用来判断两个对象是否是同一个实例对象
(即内存地址指向是否一致)
class CJLTeacher: Equatable{ var age: Int? var name: String? init(age: Int, name: String) { self.age = age self.name = name } static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool { return lhs.age == rhs.age && lhs.name == rhs.name } } //===:判断两个对象是否是同一个 var t = CJLTeacher(age: 18, name: "CJL") var t1 = t t1.age = 20 print(t == t1) <!--打印结果--> true
除了==,还有!=以及其他的运算符
Comparable协议
除了Equatable,还有Comparable协议
,其中的运算符有:< 、<=、>=、> 、...、..<、
等
public protocol Comparable : Equatable { static func < (lhs: Self, rhs: Self) -> Bool static func <= (lhs: Self, rhs: Self) -> Bool static func >= (lhs: Self, rhs: Self) -> Bool static func > (lhs: Self, rhs: Self) -> Bool } extension Comparable { public static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self> ...... }
Struct重写 < 运算符
- 以struct为例,遵循
Comparable协议
,重写 < 运算符
//1、struct遵守Comparable协议 struct CJLTeacher: Comparable{ var age: Int var name: String //重载 < 符号 static func < (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool { return lhs.age < rhs.age } } var t = CJLTeacher(age: 18, name: "CJL") var t1 = CJLTeacher(age: 19, name: "CJL") print(t < t1) <!--打印结果--> true
?? 空运算符
如果当前的变量为nil,可以在??返回一个nil时的默认值
- 下面例子的打印结果是什么?
//?? 空运算符 var age: Int? = nil //?? 等价于 if le / guard let print(age ?? 20) <!--打印结果--> 20
- 进入
Optional
源码,查看??
实现
<!--返回T--> @_transparent//空运算符 public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T { switch optional { case .some(let value): return value case .none: return try defaultValue() } } <!--返回T?--> @_transparent public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T? { switch optional { case .some(let value): return value case .none: return try defaultValue() } }
从源码中分析,??
只有两种类型,一种是T
,一种是与
,主要是与 ?? 后面的返回值有关
(即简单来说,就是??后是什么类型,??返回的就是什么类型
),如下所示
- ??后面是age1,而age1的类型是Int?,所以t的类型是
Int?
如果??是30呢? -- 类型是Int
如果??是String呢? -- 会报错,??要求类型一致(跟是否是可选类型无关)
可选链
可选链 则意味着 允许在一个链上来访问当前的属性/方法,如下所示
//***************6、可选链*************** class CJLTeacher{ var name: String? var subject: CJLSubject? } class CJLSubject { var subjectName: String? func test(){print("test")} } var s = CJLSubject() var t = CJLTeacher() //可选链访问属性 if let tmp = t.subject?.subjectName{ print("tmp不为nil") }else{ print("tmp为nil") } //可选链访问方法 t.subject?.test()
运行结果如下,因为s为nil,所以属性和方法都不会往下执行
unsafelyUnwrapped(Optional.swift中的)
这个和强制解包的内容是一致的,如下所示
//***************7、unsafelyUnwrapped 和强制解包内容是一致的 var age: Int? = 30 print(age!) print(age.unsafelyUnwrapped) <!--打印结果--> 30 30 //***************如果age是nil var age: Int? = 30 print(age!) print(age.unsafelyUnwrapped)
age是nil的结果和强制解包一致,程序会崩溃
官方对其的描述如下
- 这里的
-O
,是指target -> Build Setting -> Optimization Level
设置成-O时,如果使用的是age.unsafelyUnwrapped
,则不检查这个变量是否为nil, - 1、设置
Optimization Level
为Fastest, Smallest[-Os]
- 2、
edit Scheme -> Run -> Info -> Build Configuration
改为release
模式,然后再次运行发现,没有崩溃,与官方所说是一致的
区分as、 as? 和 as!
as
将类型转换为其他类型
var age: Int = 10 var age1 = age as Any print(age1) var age2 = age as AnyObject print(age2) <!--打印结果--> 10 10
as?
将类型转换为其他可选类型
var age: Int = 10 //as? //as? 不确定类型是Double,试着转换下,如果转换失败,则返回nil var age3 = age as? Double print(age3) <!--打印结果--> nil
此时的age3的类型是Double?
as!
:强制转换为其他类型
var age: Int = 10 //as! 强制转换为其他类型 var age4 = age as! Double print(age4)
运行结果如下,会崩溃
SIL分析
查看以下代码的SIL文件
var age: Int = 10 var age3 = age as? Double var age4 = age as! Double
- 常规使用:如果可以明确类型,则可以直接使用as!
//常规使用 var age: Any = 10 func test(_ age: Any) -> Int{ return (age as! Int) + 1 } print(test(age)) <!--打印结果--> 11
使用建议
- 如果
能确定
的类型,使用as!
即可 - 如果是
不能确定
的,使用as?
即可
总结
Optional
的本质是enum
,所以可以使用模式匹配
来匹配Optional的值- Optional的
解包方式
有两种:
- 1、
强制解包
:一旦为nil,程序会崩溃 - 2、
可选值绑定
:if let
(只能在if流程的作用域内访问)、guard let
- Equatable协议:
- 对于
swift标准库中的绝大部分类型
都默认实现了Equatable
协议 - 对于自定义
Struct
类型,仅需要遵守Equatable
协议 - 对于自定义
class
类型,除了需要遵守Equatable
协议,还需要自己实现Equatable
协议的方法
- 区分 == vs ===
==
相当于 equal to,用于判断两个值是否相等
===
是用来判断两个对象是否是同一个实例对象
(即内存地址指向是否一致)
- Comparable协议:
- 对于自定义类型,需要遵循
Comparable
协议,并重写运算符
??
空运算符:??只有两种类型,一种是T
,一种是T?
,主要是与 ?? 后面的返回值有关(即简单来说,就是??后是什么类型,??返回的就是什么类型
)
- 可选链:允许在一个链上来访问当前的属性/方法,如果
为nil
,则不会执行?后的属性/方法
unsafelyUnwrapped
:与强制解包类似,但是如果项目中设置target -> Build Setting -> Optimization Level
设置成-O时,如果使用的是age.unsafelyUnwrapped
,则不检查这个变量是否为nil- 区分 as、as?、 as!
as
将类型转换为其他类型as?
将类型转换为其他可选类型
as!
强制转换为其他类型- 使用建议:能确定使用
as!
,不能确定使用as?