前言
本篇文章主要讲解一下,之前用到过但是没有仔细分析的几个知识点 Optional
可选类型、Equatable
协议和Comparable
协议,然后会补充关于解包
的相关场景,最后大致说明一下Swift中的访问控制权限
。
一、Optional
首先来看看Optional
可选类型,这个关键字在Swift中主要是用来处理值缺失
的情况,有以下2种情况
- 有值,且等于x
- 没有值
1.1 源码分析
先来看看关于Optional
的底层源码
上图可见,Optional
的本质是enum
,当前枚举接收一个泛型参数Wrapped
,当前Some的关联值
就是当前的Wrapper
,所以下面两种写法完全等价
var age: Int? = 10
// 等价于
var age1: Optional = 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
......
}
3.1 结构体重写 < 运算符
修改上面的例子
3.2 ??运算符
??运算符
也是经常被使用的,表示 如果当前的变量值为nil时,可以在??运算符
后面再给个默认值,例如
var age: Int? = nil
//?? 等价于 if let / guard let
print(age ?? 20)
我们先看看??
函数的声明
接着我们去Swift源码中搜索??
,找到实现
上图可知 ??只有两种类型,一种是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的知识点 访问控制权限
,都是些大家在平常开发中碰到的知识点,希望大家掌握。