Swift Optional & Equatable+Compa
前言
本篇文章主要讲解一下,之前用到过但是没有仔细分析的几个知识点 👉 Optional可选类型、Equatable协议和Comparable协议,然后会补充关于解包的相关场景,最后大致说明一下Swift中的访问控制权限。
一、Optional
首先来看看Optional可选类型,这个关键字在Swift中主要是用来处理值缺失的情况,有以下2种情况👇
- 有值,且等于x
- 没有值
1.1 源码分析
先来看看关于Optional的底层源码👇
上图可见,Optional的本质是enum,当前枚举接收一个泛型参数Wrapped,当前Some的关联值就是当前的Wrapper,所以下面两种写法完全等价👇
var age: Int? = 10
// 等价于
var age1: Optional<Int> = Optional(5)
1.2 匹配模式
跟我们之前讲解Swift 枚举一样,接着我们来看看Optinal的匹配模式,示例👇
//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")
}
1.3 Optional解包
解包的意思就是获取值,因为Optional可选项是对值做了一层“包装”,我们得拆开这个“包装”才能拿到值。解包有2种方式👇
-
强制解包👉 好处是省事,坏处是解包的值是nil会导致程序crash崩溃!
var age: Int? = nil
print(age!)
运行崩溃👇
- 通过可选项绑定:判断当前的可选项是否有值
-
if - let👉 如果有值,则会进入if流程 -
guard - let👉 如果为nil,则会进入else流程
-
var age: Int? = nil
// 方式一
if let age = age{
//如果age不为nil,则打印
print(age)
}
// 方式二
func test() {
guard let tmp = age else {
print("age为nil")
return
}
print(tmp)
}
if-let 可以配合else使用,也可以直接在if中添加return,表示后续代码都是else场景下,节省了else的{},提高代码可读性👇
var age: Int? = nil
// 可以配合return使用, 省了else的{}
if let value = age {
print("value \(value)")
return
}
if let value1 = age {
print("value1 \(value1)")
} else {
print("age 为 nil")
}
注意:
- 使用
return关键字,必须将该代码块放入函数体中guard-let是具备守护功能,它必须搭配return使用,排除异常情况,守护guard(作用域)之后的代码。
if-let和guard-let的使用建议
实际开发过程中,我们希望代码从上至下能清晰表达主线流程。
-
guard-let一般用于处理非主线异常情况,直接return出去,守护主线代码。 - if-let一般用于处理
主线重要场景。 -
if-let创建的内容,仅在if 作用域内可访问;guard-let创建的内容,是供guard 作用域外访问。
二、Equatable协议
Swift中的类型,可以通过遵循Equatable协议来使用相等运算符(==)和不等运算符(!=)。
绝大多数类型默认实现Equatable协议`👇
上图可见,Int String Double Float均遵循了。
2.1 Optional中的Equatable协议
继续回到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
}
}
}
2.2 自定义类型实现Equatable协议
对于自定义的类型,如果想实现 ==,应该怎么办呢?
2.2.1自定义struct类型
首先我们看看结构体类型👇
struct LGTeacher {
var age: Int
var name: String
}
var t = LGTeacher(age: 17, name: "Kody")
var t1 = LGTeacher(age: 18, name: "Cooci")
print(t == t1)
然后遵循Equatable协议实现👇
上图可知,结构体直接实现Equatable协议,即可比较,为什么呢?我们去看SIL代码👇
系统默认实现了==方法,然后调用的是__derived_struct_equals去做的比较👇
2.2.2自定义Class类型
接下来我们看看类class的Equatable协议实现,将LGPerson改为class👇
报错提示缺少初始化方法,那么添加👇
接着报错,未实现Equatable协议,那么实现👇
属性是Optional类型
我们查看SIL代码👇
比较调用的是Optinal的==方法👇
2.2.3 结构体嵌套类
上图可知,结构体嵌套类,即值类型中包含引用类型的实例,必须实现引用类型的Equatable (==)函数,查看SIL👇
2.2.4 == 与 ===
在我们平常的开发中,经常会用到==,但Swift中还有个===,它俩之间有什么区别呢?👇
-
==用来检验值是否相等,需要遵循Equatable协议 -
===是用来检验两个对象是否是同一个实例对象👉内存地址是否相等,不支持值类型,仅支持类实例(AnyObject?)使用。
@inlinable public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool
@inlinable public func !== (lhs: AnyObject?, rhs: AnyObject?) -> Bool
三、Comparable协议
Comparable协议继承了Equatable协议,并多支持了其他比较方式 👉 其中的运算符有:< 、<=、>=、> 、...、..<等
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>
......
}
3.1 结构体重写 < 运算符
修改上面的例子👇
3.2 ??运算符
??运算符也是经常被使用的,表示 👉 如果当前的变量值为nil时,可以在??运算符后面再给个默认值,例如👇
var age: Int? = nil
//?? 等价于 if let / guard let
print(age ?? 20)
我们先看看??函数的声明👇
接着我们去Swift源码中搜索?? <T>(optional:,找到实现👇
上图可知 👉 ??只有两种类型,一种是T,一种是T?,主要是与 ?? 后面的返回值有关,简单来说 👉 ??后是什么类型,??返回的就是什么类型👇
??后面是age1,而age1的类型是Int?,所以t的类型是 Int?
如果??后面是18,Int类型 👉 t1的类型就是Int类型
如果??后面是String呢? 👉 会报错,??要求类型一致,跟是否Optional类型无关。
补充:可选链
可选链 👉 允许在一个链上来访问当前的属性/方法,示例👇
class LGSubject {
var subjectName: String?
func test(){print("test")}
}
class LGTeacher{
var name: String?
var subject: LGSubject?
}
var s = LGSubject()
var t = LGTeacher()
//可选链访问属性
if let tmp = t.subject?.subjectName{
print("tmp: \(tmp)")
}else{
print("tmp为nil")
}
//可选链访问方法
t.subject?.test()
修改赋值👇
四、解包
接着我们来讲解一下关于解包的几个关键字。
4.1 unsafelyUnwrapped
unsafelyUnwrapped是Optional.swift中的对可选型变量的一个解包,它解包的结果和强制解包的结果是一样的,例如👇
// age有值
var age: Int? = 30
print(age!)
print(age.unsafelyUnwrapped)
// age1是nil
var age1: Int? = nil
print(age1!)
print(age1.unsafelyUnwrapped)
age是nil时的结果和强制解包一致 👉 程序会崩溃。接下来我们看看unsafelyUnwrapped的声明👇
这里的-O,是指Build Setting中的配置选项Optimization Level设置成-O,如果是Fastest, Smallest[-Os],即我们使用release模式👇
再次运行👇
4.2 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? 不确定类型是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)
使用建议
- 如果
能确定类型,使用as!即可。例如👇
// 常规使用
var age: Any = 10
func test(_ age: Any) -> Int{
return (age as! Int) + 1
}
print(test(age))
// 打印结果
11
- 如果
不能确定的,使用as?即可
五、访问控制权限
最后,我们再来了解一下Swift中的与访问控制权限相关的几个关键字。
5.1 private
访问级别仅在当前定义的作用域内有效(单例中使用过👇)。
class LGTeacher{
static let shareInstance = LGTeacher()
private init(){}
}
var t = LGTeacher.shareInstance
5.2 filePrivate
访问限制仅限制在当前定义的源文件中。
fileprivate class LGPartTimeTeacher: LGTeacher{
var partTime: Double?
init(_ partTime: Double) {
super.init()
self.partTime = partTime
}
}
如果上述代码是在Teacher.swift中,那么在main.swift中无法访问LGPartTimeTeacher。
再看下面这个示例👇
上图报错的原因 👉 let pt 默认的权限是 Internal的(接下来会讲),而LGPartTimeTeacher的访问权限是fileprivate的,Internal权限大于fileprivate,系统不允许这样,所以提示错误。
修改
- 方式一 👉 需要使用
private / fileprivate修饰pt👇
private let pt = LGPartTimeTeacher(10.0)
//或者
fileprivate let pt = LGPartTimeTeacher(10.0)
- 方式二 👉 如果直接
定义在方法中的,可以不用访问权限修饰符
func test(){
let pt = LGPartTimeTeacher(10.0)
}
5.3 Internal
默认访问级别,允许定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问(例如 import Foundation,其中Foundation就是一个模块)。
// main.swift中
import Foundation
class LGTeacher{
init(){}
}
let t = LGTeacher()
// 另一个custom.swift中
import AppKit
//访问main.swift中t,报错:Expressions are not allowed at the top level
print(t)
上图报错原因 👉 t在main.swift文件中的默认权限是Internal,只能在同一个模块内使用,其属于Foundation模块,而custom.swift文件中不能调用t,是因为其属于AppKit模块,与Foundation并不是同一个模块,所以不能访问。
5.4 public
开放式访问,使我们能够在其定义模块的任何源文件中使用代码,并且可以从另一个源文件访问源文件。但是只能在定义的模块中继承和子类重写。
5.5 open
最不受限制的访问级别,可以在任意地方、任意模块间被继承、定义、重写。
public & open区别
这也是我们经常容易混淆的点,区别主要是👇
public不可继承,open可继承!
总结
本篇文章相对于前面所讲解的Mirror、枚举和闭包知识点而言,很简单。主要介绍了Optional可选类型的概念,底层的实现和使用场景,然后顺着Optional的匹配模式,讲解了Equatable+Comparable协议,顺便总结了解包的几种方式,最后讲解了Swift不同于OC的知识点 👉 访问控制权限,都是些大家在平常开发中碰到的知识点,希望大家掌握。