• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Swift探索10:Optional和amp;Equatable和amp;Comparable和amp;访问控制权限

武飞扬头像
changcongcong_ios
帮助1

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

Optional分析

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

  • 有值,且等于x

  • 没有值

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

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

  1.  
    var age: Int? = 10
  2.  
    等价于
  3.  
    var age1: Optional<Int> = Optional(5)
  • 【Optional使用模式匹配】:既然Optional的本质是枚举,那么也可以使用模式匹配来匹配对应的值,如下所示

  1.  
    //1、声明一个可选类型的变量
  2.  
    var age: Int? = 10
  3.  
    //2、通过模式匹配来匹配对应的值
  4.  
    switch age{
  5.  
    case nil:
  6.  
    print("age 是个空值")
  7.  
    case .some(let val):
  8.  
    print("age的值是\(val)")
  9.  
    }
  10.  
     
  11.  
    <!--或者这样写-->
  12.  
    switch age{
  13.  
    case nil:
  14.  
    print("age 是个空值")
  15.  
    case .some(10):
  16.  
    print("age的值是10")
  17.  
    default:
  18.  
    print("unKnow")
  19.  
    }
学新通
  • 【Optional解包】:因为是Optional类型,当我们需要从其中拿到我们想要的值时,需要对其进行解包,因为当前的可选项是对值做了一层包装的,有以下两种方式:

    • if let:如果有值,则会进入if流程

    • guard let:如果为nil,则会进入else流程

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

      学新通

      强制解包为nil崩溃

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

  1.  
    //3、可选项解包
  2.  
    var age: Int? = nil
  3.  
     
  4.  
    //3-1、强制解包
  5.  
    //如果age为nil,则程序崩溃
  6.  
    print(age!)
  7.  
     
  8.  
    //3-2、可选值绑定
  9.  
    <!--方式一-->
  10.  
    if let age = age{
  11.  
    //如果age不为nil,则打印
  12.  
    print(age)
  13.  
    }
  14.  
    <!--方式二-->
  15.  
    guard let tmp = age else {
  16.  
    print("age为nil")
  17.  
    return
  18.  
    }
  19.  
    print(tmp)
学新通

可选项绑定总结

  • 1、使用if let创建的内容当中age仅仅只能在当前if分支的大括号内访问

  • 2、使用guard let定义的tmp在当前大括号外部也是能访问的

Equatable协议

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

  1.  
    var age: Int? = 10
  2.  
    var age1: Optional<Int> = Optional(5)
  3.  
     
  4.  
    age == age1
  • 继续回到Optional源码中,可以看到Optional遵循了Equatable协议

  1.  
    extension Optional: Equatable where Wrapped: Equatable {
  2.  
     
  3.  
    ......
  4.  
     
  5.  
    @inlinable
  6.  
    public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
  7.  
    switch (lhs, rhs) {
  8.  
    case let (l?, r?):
  9.  
    return l == r
  10.  
    case (nil, nil):
  11.  
    return true
  12.  
    default:
  13.  
    return false
  14.  
    }
  15.  
    }
  16.  
    }
学新通

swift标准库中的类型

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

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

  1.  
    var age2: Int = 20
  2.  
    var isEqual = age1 == age2
  3.  
    print(isEqual)
  4.  
     
  5.  
    <!--打印结果-->
  6.  
    false

自定义类型

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

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

    学新通

    报错示意

  • 可以通过遵循Equatable协议实现,如下所示

  1.  
    //2、自定义类型如何实现Equatable协议
  2.  
    struct CJLTeacher: Equatable{
  3.  
    var age: Int
  4.  
    var name: String
  5.  
    }
  6.  
    var t = CJLTeacher(age: 18, name: "CJL")
  7.  
    var t1 = CJLTeacher(age: 19, name: "CJL")
  8.  
    print(t == t1)
  9.  
     
  10.  
    <!--打印结果-->
  11.  
    false
  12.  
    //如果将t1中的age改成18,打印结果是什么
  13.  
    true

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

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

    学新通

    SIL验证-1

    • 查看__derived_struct_equals方法的实现

      学新通

      SIL验证-2

疑问:如果是Class类型呢?

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

学新通

class仅遵守Equatable协议报错

  • class中手动实现Equatable协议的方法

  1.  
    //3、如果是class类型呢?需要手动实现Equatable协议的方法
  2.  
    class CJLTeacher: Equatable{
  3.  
     
  4.  
    var age: Int
  5.  
    var name: String
  6.  
     
  7.  
    init(age: Int, name: String) {
  8.  
    self.age = age
  9.  
    self.name = name
  10.  
    }
  11.  
     
  12.  
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
  13.  
    return lhs.age == rhs.age && lhs.name == rhs.name
  14.  
    }
  15.  
     
  16.  
    }
  17.  
    var t = CJLTeacher(age: 18, name: "CJL")
  18.  
    var t1 = CJLTeacher(age: 19, name: "CJL")
  19.  
    print(t == t1)
学新通
  • 如果class中的age和name都是Optional呢?

  1.  
    //4、如果class中的属性都是可选类型呢?底层是调用Optional==来判断
  2.  
    class CJLTeacher: Equatable{
  3.  
     
  4.  
    var age: Int?
  5.  
    var name: String?
  6.  
     
  7.  
    init(age: Int, name: String) {
  8.  
    self.age = age
  9.  
    self.name = name
  10.  
    }
  11.  
     
  12.  
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
  13.  
    return lhs.age == rhs.age && lhs.name == rhs.name
  14.  
    }
  15.  
    }
  16.  
    var t = CJLTeacher(age: 18, name: "CJL")
  17.  
    var t1 = CJLTeacher(age: 19, name: "CJL")
  18.  
    print(t == t1)
学新通

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

学新通

class的SIL验证

区分 == vs ===

  • == 相当于 equal to,用于判断两个值是否相等

  • === 是用来判断 两个对象是否是同一个实例对象(即内存地址指向是否一致)

  1.  
    class CJLTeacher: Equatable{
  2.  
     
  3.  
    var age: Int?
  4.  
    var name: String?
  5.  
     
  6.  
    init(age: Int, name: String) {
  7.  
    self.age = age
  8.  
    self.name = name
  9.  
    }
  10.  
     
  11.  
    static func == (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
  12.  
    return lhs.age == rhs.age && lhs.name == rhs.name
  13.  
    }
  14.  
    }
  15.  
    //===:判断两个对象是否是同一个
  16.  
    var t = CJLTeacher(age: 18, name: "CJL")
  17.  
    var t1 = t
  18.  
    t1.age = 20
  19.  
    print(t == t1)
  20.  
     
  21.  
    <!--打印结果-->
  22.  
    true
学新通

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

Comparable协议

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

  1.  
    public protocol Comparable : Equatable {
  2.  
    static func < (lhs: Self, rhs: Self) -> Bool
  3.  
     
  4.  
    static func <= (lhs: Self, rhs: Self) -> Bool
  5.  
     
  6.  
    static func >= (lhs: Self, rhs: Self) -> Bool
  7.  
     
  8.  
    static func > (lhs: Self, rhs: Self) -> Bool
  9.  
    }
  10.  
    extension Comparable {
  11.  
    public static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self>
  12.  
    ......
  13.  
    }

Struct重写 < 运算符

  • 以struct为例,遵循Comparable协议,重写 < 运算符

  1.  
    //1、struct遵守Comparable协议
  2.  
    struct CJLTeacher: Comparable{
  3.  
     
  4.  
    var age: Int
  5.  
    var name: String
  6.  
     
  7.  
    //重载 < 符号
  8.  
    static func < (lhs: CJLTeacher, rhs: CJLTeacher) -> Bool {
  9.  
    return lhs.age < rhs.age
  10.  
    }
  11.  
    }
  12.  
    var t = CJLTeacher(age: 18, name: "CJL")
  13.  
    var t1 = CJLTeacher(age: 19, name: "CJL")
  14.  
    print(t < t1)
  15.  
     
  16.  
    <!--打印结果-->
  17.  
    true
学新通

?? 空运算符

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

  • 下面例子的打印结果是什么?

  1.  
    //?? 空运算符
  2.  
    var age: Int? = nil
  3.  
    //?? 等价于 if le / guard let
  4.  
    print(age ?? 20)
  5.  
     
  6.  
     
  7.  
    <!--打印结果-->
  8.  
    20
  • 进入Optional源码,查看??实现

  1.  
    <!--返回T-->
  2.  
    @_transparent//空运算符
  3.  
    public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
  4.  
    rethrows -> T {
  5.  
    switch optional {
  6.  
    case .some(let value):
  7.  
    return value
  8.  
    case .none:
  9.  
    return try defaultValue()
  10.  
    }
  11.  
    }
  12.  
     
  13.  
    <!--返回T?-->
  14.  
    @_transparent
  15.  
    public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
  16.  
    rethrows -> T? {
  17.  
    switch optional {
  18.  
    case .some(let value):
  19.  
    return value
  20.  
    case .none:
  21.  
    return try defaultValue()
  22.  
    }
  23.  
    }
学新通

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

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

    学新通

    ??是Int?

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

    学新通

    ??是30

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

    学新通

    ??是String

可选链

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

  1.  
    //***************6、可选链***************
  2.  
    class CJLTeacher{
  3.  
    var name: String?
  4.  
    var subject: CJLSubject?
  5.  
     
  6.  
    }
  7.  
     
  8.  
    class CJLSubject {
  9.  
    var subjectName: String?
  10.  
    func test(){print("test")}
  11.  
    }
  12.  
     
  13.  
    var s = CJLSubject()
  14.  
    var t = CJLTeacher()
  15.  
     
  16.  
    //可选链访问属性
  17.  
    if let tmp = t.subject?.subjectName{
  18.  
    print("tmp不为nil")
  19.  
    }else{
  20.  
    print("tmp为nil")
  21.  
    }
  22.  
    //可选链访问方法
  23.  
    t.subject?.test()
学新通

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

学新通

可选链为nil的运行结果

unsafelyUnwrapped(Optional.swift中的)

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

  1.  
    //***************7、unsafelyUnwrapped 和强制解包内容是一致的
  2.  
    var age: Int? = 30
  3.  
    print(age!)
  4.  
    print(age.unsafelyUnwrapped)
  5.  
     
  6.  
    <!--打印结果-->
  7.  
    30
  8.  
    30
  9.  
     
  10.  
     
  11.  
    //***************如果age是nil
  12.  
    var age: Int? = 30
  13.  
    print(age!)
  14.  
    print(age.unsafelyUnwrapped)

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

学新通

unsafelyUnwrapped为nil的崩溃示意

  • 官方对其的描述如下

    学新通

    unsafelyUnwrapped官方说明


    这里的-O,是指target -> Build Setting -> Optimization Level设置成-O时,如果使用的是age.unsafelyUnwrapped,则不检查这个变量是否为nil,

  • 1、设置Optimization Level 为Fastest, Smallest[-Os]

  • 2、edit Scheme -> Run -> Info -> Build Configuration改为release模式,然后再次运行发现,没有崩溃,与官方所说是一致的

    学新通

    根据官方描述调测

区分as、 as? 和 as!

  • as 将类型转换为其他类型

  1.  
    var age: Int = 10
  2.  
     
  3.  
    var age1 = age as Any
  4.  
    print(age1)
  5.  
     
  6.  
    var age2 = age as AnyObject
  7.  
    print(age2)
  8.  
     
  9.  
    <!--打印结果-->
  10.  
    10
  11.  
    10
  • as? 将类型转换为 其他可选类型

  1.  
    var age: Int = 10
  2.  
    //as?
  3.  
    //as? 不确定类型是Double,试着转换下,如果转换失败,则返回nil
  4.  
    var age3 = age as? Double
  5.  
    print(age3)
  6.  
     
  7.  
    <!--打印结果-->
  8.  
    nil

此时的age3的类型是Double?

学新通

as?运行结果

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

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

运行结果如下,会崩溃

学新通

as!崩溃运行结果

SIL分析

查看以下代码的SIL文件

  1.  
    var age: Int = 10
  2.  
    var age3 = age as? Double
  3.  
    var age4 = age as! Double

学新通

as的SIL分析

  • 常规使用:如果可以明确类型,则可以直接使用as!

  1.  
    //常规使用
  2.  
    var age: Any = 10
  3.  
    func test(_ age: Any) -> Int{
  4.  
    return (age as! Int) 1
  5.  
    }
  6.  
    print(test(age))
  7.  
     
  8.  
    <!--打印结果-->
  9.  
    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! 强制转换为其他类

      1.  
        //8-1、private:访问级别`仅在当前定义的作用域内有效
      2.  
        class CJLTeacher{
      3.  
        static let shareInstance = CJLTeacher()
      4.  
        private init(){}
      5.  
        }
      6.  
        var t = CJLTeacher.shareInstance

      filePrivate

      filePrivate:访问限制仅限制在当前定义的源文件中

      1.  
        <!--1、在access.swift文件中定义CJLPartTimeTeacher-->
      2.  
        fileprivate class CJLPartTimeTeacher: CJLTeacher{
      3.  
        var partTime: Double?
      4.  
        init(_ partTime: Double) {
      5.  
        super.init()
      6.  
        self.partTime = partTime
      7.  
        }
      8.  
        }
      9.  
         
      10.  
        <!--2、在main.swift中无法访问CJLPartTimeTeacher-->

      学新通

      调用报错提示

      在access.swift文件中定义一个CJLPartTimeTeacher全局变量,系统报错,其主要原因是 pt 默认的权限是 Internal的,而CJLPartTimeTeacher的访问权限是fileprivate的,pt的权限大于CJLPartTimeTeacher,系统不允许这样,所以提示错误

      学新通

      filePrivate报错提示

    • 需要使用private / fileprivate修饰pt

      1.  
        private let pt = CJLPartTimeTeacher(10.0)
      2.  
        //或者
      3.  
        fileprivate let pt = CJLPartTimeTeacher(10.0)

      如果是直接定义在方法中的,可以不用访问权限修饰符

      1.  
        func test(){
      2.  
        let pt = CJLPartTimeTeacher(10.0)
      3.  
        }

      Internal

      Internal:默认访问级别,允许定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问(例如 import Foundation,其中Foundation就是一个模块)

      1.  
        <!--1、main.swift-->
      2.  
        import Foundation
      3.  
        class CJLTeacher{
      4.  
        init(){}
      5.  
        }
      6.  
        let t = CJLTeacher()
      7.  
         
      8.  
        <!--2、custom-->
      9.  
        import AppKit
      10.  
        //访问main.swift中t,报错:Expressions are not allowed at the top level
      11.  
        print(t)

      学新通

      Internal报错提示
      报错的主要原因是tmain.swift文件中的默认权限是Internal,只能在同一个模块内使用,其属于Foundation模块,而custom.swift文件中不能调用t,是因为其属于AppKit模块,与Foundation并不是同一个模块,所以不能访问

      public

      public:开放式访问,使我们能够在其定义模块的任何源文件中使用代码,并且可以从另一个源文件访问源文件。但是只能在定义的模块中继承和子类重写

      open

      open:最不受限制的访问级别,可以在任意地方、任意模块间被继承、定义、重写

      public与open的区别:

    • public不可继承

    • open可继承

    • 总结:

    • 没有写访问控制权限关键字时,默认访问权限是internal

    • 访问控制权限从高到低的顺序:open > public > internal > filePrivate > private

      • 1、private:访问级别仅在当前定义的作用域内有效

      • 2、filePrivate:访问限制仅限制在当前定义的源文件中

      • 3、Internal:默认访问级别,允许定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问

      • 4、public:开放式访问,使我们能够在其定义模块的任何源文件中使用代码,并且可以从另一个源文件访问源文件。但是只能在定义的模块中继承和子类重写

      • 5、open:最不受限制的访问级别,可以在任意地方、任意模块间被继承、定义、重写

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgicfhj
系列文章
更多 icon
同类精品
更多 icon
继续加载