Swift-进阶 10:可选类型Optional & Equatable+Comparable协议

简介: Swift-进阶 10:可选类型Optional & Equatable+Comparable协议

本文主要分析Optional源码、Equatable+Comparable协议


Optional分析


swift中的可选类型(Optional),用于处理值缺失的情况,有以下两种情况


  • 有值,且等于x
  • 没有值


这点可以通过swift-source->Optional.swift源码(CMD+P,搜索Optional)源码来印证

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
    ......
  //为nil
  case none
    ......
  //有值
  case some(Wrapped)
  ......
}
  • 通过源码可知,Optional的本质是enum,当前枚举接收一个泛型参数Wrapped,当前Some的关联值就是当前的Wrapper,下面两种写法完全等价


var age: Int? = 10
等价于
var age1: Optional<Int> = Optional(5)
  • 【Optional使用模式匹配】:既然Optional的本质是枚举,那么也可以使用模式匹配来匹配对应的值,如下所示
//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")
}

【Optional解包】:因为是Optional类型,当我们需要从其中拿到我们想要的值时,需要对其进行解包,因为当前的可选项是对值做了一层包装的,有以下两种方式:


  • 1、强制解包:好处是省事,坏处是一旦解包的值是nil,那么程序就会崩溃

image.png

  • 2、通过可选项绑定:判断当前的可选项是否有值


  • if let:如果有值,则会进入if流程
  • guard let:如果为nil,则会进入else流程
//3、可选项解包
var age: Int? = nil
//3-1、强制解包
//如果age为nil,则程序崩溃
print(age!)
//3-2、可选值绑定
<!--方式一-->
if let age = age{
    //如果age不为nil,则打印
    print(age)
}
<!--方式二-->
guard let tmp = age else {
    print("age为nil")
    return
}
print(tmp)

可选项绑定总结


  • 1、使用if let创建的内容当中age仅仅只能在当前if分支的大括号内访问
  • 2、使用guard let定义的tmp在当前大括号外部也是能访问的


Equatable协议


在上面的例子中,可以通过==判断两个可选项是否相等,原因是因为Optinal在底层遵循了Equatable协议

var age: Int? = 10
var age1: Optional<Int> = Optional(5)
age == age1
  • 继续回到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
    }
  }
}

swift标准库中的类型


在swift中的类型,可以通过遵循Equatable协议来使用相等运算符(==)不等运算符(!=)比较两个值相等还是不相等,Swift标准库中绝大多数类型都默认实现了Equatable协议


例如下面的例子,对于Int类型来说,系统默认实现了 ==

var age2: Int = 20
var isEqual = age1 == age2
print(isEqual)
<!--打印结果-->
false

自定义类型


对于自定义的类型,如果想实现 ==,应该怎么办呢?


  • 如果像下面这样写,是会直接报错的

image.png

  • 可以通过遵循Equatable协议实现,如下所示
//2、自定义类型如何实现Equatable协议
struct CJLTeacher: Equatable{
    var age: Int
    var name: String
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)
<!--打印结果-->
false
//如果将t1中的age改成18,打印结果是什么
true

为什么呢?其根本原因是因为遵守了Equatable协议,系统默认帮我们实现了==方法


  • 查看SIL方法,是否如我们猜想的一样?经过验证确实与我们猜测结论是一致的
    image.png

查看__derived_struct_equals方法的实现

image.png

疑问:如果是Class类型呢?


如果像Struct那么写,会报错,提示需要自己实现Equatable协议的方法

image.png


  • class中手动实现Equatable协议的方法
//3、如果是class类型呢?需要手动实现Equatable协议的方法
class CJLTeacher: Equatable{
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)
  • 如果class中的age和name都是Optional呢?
//4、如果class中的属性都是可选类型呢?底层是调用Optional的==来判断
class CJLTeacher: Equatable{
    var age: Int?
    var name: String?
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t == t1)

查看其SIL文件可以验证这一点:底层是通过调用Optional的==来判断

image.png


区分 == vs ===


  • == 相当于 equal to,用于判断两个值是否相等
  • === 是用来判断 两个对象是否是同一个实例对象(即内存地址指向是否一致)
class CJLTeacher: Equatable{
    var age: Int?
    var name: String?
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age == rhs.age && lhs.name == rhs.name
    }
}
//===:判断两个对象是否是同一个
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = t
t1.age = 20
print(t == t1)
<!--打印结果-->
true

除了==,还有!=以及其他的运算符


Comparable协议


除了Equatable,还有Comparable协议,其中的运算符有:< 、<=、>=、> 、...、..<、

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

Struct重写 < 运算符

  • 以struct为例,遵循Comparable协议,重写 < 运算符
//1、struct遵守Comparable协议
struct CJLTeacher: Comparable{
    var age: Int
    var name: String
    //重载 < 符号
    static func < (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
        return lhs.age < rhs.age
    }
}
var t = CJLTeacher(age: 18, name: "CJL")
var t1 = CJLTeacher(age: 19, name: "CJL")
print(t < t1)
<!--打印结果-->
true

?? 空运算符


如果当前的变量为nil,可以在??返回一个nil时的默认值


  • 下面例子的打印结果是什么?
//?? 空运算符
var age: Int? = nil
//?? 等价于 if le / guard let
print(age ?? 20)
<!--打印结果-->
20
  • 进入Optional源码,查看??实现
<!--返回T-->
@_transparent//空运算符
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}
<!--返回T?-->
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
    rethrows -> T? {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

从源码中分析,??只有两种类型,一种是T,一种是,主要是与 ?? 后面的返回值有关(即简单来说,就是??后是什么类型,??返回的就是什么类型),如下所示


  • ??后面是age1,而age1的类型是Int?,所以t的类型是 Int?

image.png

如果??是30呢? -- 类型是Int

image.png如果??是String呢? -- 会报错,??要求类型一致(跟是否是可选类型无关)


image.png


可选链


可选链 则意味着 允许在一个链上来访问当前的属性/方法,如下所示

//***************6、可选链***************
class CJLTeacher{
    var name: String?
    var subject: CJLSubject?
}
class CJLSubject {
    var subjectName: String?
    func test(){print("test")}
}
var s = CJLSubject()
var t = CJLTeacher()
//可选链访问属性
if let tmp = t.subject?.subjectName{
    print("tmp不为nil")
}else{
    print("tmp为nil")
}
//可选链访问方法
t.subject?.test()

运行结果如下,因为s为nil,所以属性和方法都不会往下执行

image.png


unsafelyUnwrapped(Optional.swift中的)


这个和强制解包的内容是一致的,如下所示

//***************7、unsafelyUnwrapped 和强制解包内容是一致的
var age: Int? = 30
print(age!)
print(age.unsafelyUnwrapped)
<!--打印结果-->
30
30
//***************如果age是nil
var age: Int? = 30
print(age!)
print(age.unsafelyUnwrapped)

age是nil的结果和强制解包一致,程序会崩溃

image.png

官方对其的描述如下

image.png

  • 这里的-O,是指target -> Build Setting -> Optimization Level设置成-O时,如果使用的是age.unsafelyUnwrapped,则不检查这个变量是否为nil,
  • 1、设置Optimization LevelFastest, Smallest[-Os]
  • 2、edit Scheme -> Run -> Info -> Build Configuration改为release模式,然后再次运行发现,没有崩溃,与官方所说是一致的


image.png


区分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?
//as? 不确定类型是Double,试着转换下,如果转换失败,则返回nil
var age3 = age as? Double
print(age3)
<!--打印结果-->
nil

此时的age3的类型是Double?

image.png

  • as! :强制转换为其他类型

var age: Int = 10
//as! 强制转换为其他类型
var age4 = age as! Double
print(age4)

运行结果如下,会崩溃

image.png

SIL分析


查看以下代码的SIL文件

var age: Int = 10
var age3 = age as? Double
var age4 = age as! Double

image.png

  • 常规使用:如果可以明确类型,则可以直接使用as!
//常规使用
var age: Any = 10
func test(_ age: Any) -> Int{
    return (age as! Int) + 1
}
print(test(age))
<!--打印结果-->
11

使用建议


  • 如果能确定的类型,使用 as! 即可
  • 如果是不能确定的,使用 as? 即可


总结


  • Optional的本质是enum,所以可以使用模式匹配来匹配Optional的值
  • Optional的解包方式有两种:
  • 1、强制解包:一旦为nil,程序会崩溃
  • 2、可选值绑定if let (只能在if流程的作用域内访问)、guard let
  • Equatable协议:
  • 对于swift标准库中的绝大部分类型都默认实现了Equatable协议
  • 对于自定义Struct类型,仅需要遵守Equatable协议
  • 对于自定义class类型,除了需要遵守Equatable协议,还需要自己实现Equatable协议的方法
  • 区分 == vs ===
  • == 相当于 equal to,用于判断两个值是否相等
  • === 是用来判断 两个对象是否是同一个实例对象(即内存地址指向是否一致)
  • Comparable协议:
  • 对于自定义类型,需要遵循Comparable协议,并重写运算符
  • ??空运算符:??只有两种类型,一种是T,一种是T?,主要是与 ?? 后面的返回值有关(即简单来说,就是??后是什么类型,??返回的就是什么类型
  • 可选链:允许在一个链上来访问当前的属性/方法,如果为nil,则不会执行?后的属性/方法
  • unsafelyUnwrapped:与强制解包类似,但是如果项目中设置target -> Build Setting -> Optimization Level设置成-O时,如果使用的是age.unsafelyUnwrapped,则不检查这个变量是否为nil
  • 区分 as、as?、 as!
  • as 将类型转换为其他类型
  • as? 将类型转换为 其他可选类型
  • as! 强制转换为其他类型
  • 使用建议:能确定使用as!,不能确定使用as?


相关文章
|
6月前
|
Swift iOS开发
Swift 语言: 什么是协议(Protocol)?如何实现和使用协议?
Swift 语言: 什么是协议(Protocol)?如何实现和使用协议?
181 2
|
6月前
|
存储 Swift
在Swift编程语言中,浮点数类型
在Swift编程语言中,浮点数类型
79 6
|
6月前
|
Swift
在Swift编程语言中,整数类型
在Swift编程语言中,整数类型
61 1
|
Swift iOS开发
9 如何在Swift中使用Optional
如何在Swift中使用Optional
64 2
|
存储 Swift C++
41 Swift不透明类型
Swift不透明类型
49 0
|
算法 Swift C++
34 Swift为了协议 关联类型
Swift为了协议 关联类型
70 0
|
存储 Swift iOS开发
31 Swift 继续聊聊协议扩展
Swift 继续聊聊协议扩展
107 0
|
6月前
|
设计模式 Swift iOS开发
【Swift开发专栏】Swift中的协议与委托模式
【4月更文挑战第30天】Swift编程语言强调协议与委托模式。协议定义了类型需实现的方法和属性,如`SomeProtocol`示例。遵循协议的类、结构体或枚举需实现协议要求。协议可继承,也可作为类型使用。委托模式让对象间通信更灵活,通过协议实现,如`DataSourceDelegate`示例。实战案例展示了在`UITableView`和自定义下载器中使用委托模式。
108 0
|
4月前
|
存储 安全 Swift
Swift高级特性:泛型与协议
【7月更文挑战第10天】Swift高级特性:泛型与协议增强代码复用与类型安全。泛型允许编写通用代码,如`swap`函数和泛型`Stack`结构体,支持类型约束如`Comparable`。协议定义行为蓝图,类型遵循协议需实现其要求。通过两者结合,构建高效灵活的代码结构。
|
6月前
|
安全 Swift
【Swift开发专栏】Swift中的可选类型与解包
【4月更文挑战第30天】Swift的可选类型(Optional)用于表示变量可能无值,如用户未填写表单或空服务器数据。可选类型用问号(?)标记,状态可为包含值或nil。解包包括强制解包(!,可能触发运行时错误)、可选绑定(在if/while中安全解包)和隐式解包(声明时带!,使用时不需显式解包)。高级用法包括可选链式调用、空合并操作符(??)和可选类型比较。理解并恰当使用这些概念能提升代码的健壮性和安全性。
61 1