前言
本篇文章主要讲解一下,之前用到过但是没有仔细分析的几个知识点 👉 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的知识点 👉 访问控制权限
,都是些大家在平常开发中碰到的知识点,希望大家掌握。