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

  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<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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容