Swift Optional & Equatable+Comparable协议 & 访问控制权限

前言

本篇文章主要讲解一下,之前用到过但是没有仔细分析的几个知识点 Optional可选类型、Equatable协议和Comparable协议,然后会补充关于解包的相关场景,最后大致说明一下Swift中的访问控制权限

一、Optional

首先来看看Optional可选类型,这个关键字在Swift中主要是用来处理值缺失的情况,有以下2种情况

  1. 有值,且等于x
  2. 没有值

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种方式

  1. 强制解包 好处是省事,坏处是解包的值是nil会导致程序crash崩溃
var age: Int? = nil
print(age!)

运行崩溃

  1. 通过可选项绑定:判断当前的可选项是否有值
    • 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")
}

注意:

  1. 使用return关键字,必须将该代码块放入函数体
  2. guard-let是具备守护功能,它必须搭配return使用,排除异常情况,守护guard(作用域)之后的代码。
if-letguard-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类型

接下来我们看看类classEquatable协议实现,将LGPerson改为class

报错提示缺少初始化方法,那么添加

接着报错,未实现Equatable协议,那么实现

属性是Optional类型

我们查看SIL代码

比较调用的是Optinal的==方法

2.2.3 结构体嵌套类

上图可知,结构体嵌套类,即值类型中包含引用类型的实例,必须实现引用类型Equatable (==)函数,查看SIL

2.2.4 =====

在我们平常的开发中,经常会用到==,但Swift中还有个===,它俩之间有什么区别呢?

  1. == 用来检验值是否相等,需要遵循Equatable协议
  2. ===是用来检验两个对象是否是同一个实例对象 内存地址是否相等,不支持值类型,仅支持类实例(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源码中搜索?? (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

unsafelyUnwrappedOptional.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!

  1. as 将类型转换为其他类型
var age: Int = 10

var age1 = age as Any
print(age1)

var age2 = age as AnyObject
print(age2)

// 打印结果
10
10
  1. as? 将类型转换为 其他可选类型
var age: Int = 10
//as? 不确定类型是Double,试着转换下,如果转换失败,则返回nil
var age3 = age as? Double
print(age3)

// 打印结果
nil

注意: 此时的age3的类型是Double?

  1. 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的知识点 访问控制权限,都是些大家在平常开发中碰到的知识点,希望大家掌握。

你可能感兴趣的:(Swift Optional & Equatable+Comparable协议 & 访问控制权限)