美文网首页iOS 底层面试EMoney 学习Swift 底层进阶
iOS开发-Swift进阶之可选类型Optional & Equ

iOS开发-Swift进阶之可选类型Optional & Equ

作者: iOS鑫 | 来源:发表于2021-06-08 14:17 被阅读0次

    swift进阶总汇

    本文主要分析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类型,当我们需要从其中拿到我们想要的值时,需要对其进行解包,因为当前的可选项是对值做了一层包装的,有以下两种方式:

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

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

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

      强制解包为nil崩溃

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

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

    可选项绑定总结

    作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

    • 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
    

    自定义类型

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

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

      报错示意

    • 可以通过遵循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方法,是否如我们猜想的一样?经过验证确实与我们猜测结论是一致的

      SIL验证-1

    • 查看__derived_struct_equals方法的实现

    SIL验证-2

    疑问:如果是Class类型呢?

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

    class仅遵守Equatable协议报错

    • 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的==来判断

    class的SIL验证

    区分 == 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?

      ??是Int?

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

      ??是30

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

    ??是String

    可选链

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

    //***************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,所以属性和方法都不会往下执行

    可选链为nil的运行结果

    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的结果和强制解包一致,程序会崩溃

    unsafelyUnwrapped为nil的崩溃示意

    • 官方对其的描述如下

      unsafelyUnwrapped官方说明

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

    • 1、设置Optimization LevelFastest, Smallest[-Os]

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

      根据官方描述调测

    区分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?

    as?运行结果

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

    运行结果如下,会崩溃

    as!崩溃运行结果

    SIL分析

    查看以下代码的SIL文件

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

    as的SIL分析

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

    使用建议

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

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

    总结

    作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

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

    相关文章

      网友评论

        本文标题:iOS开发-Swift进阶之可选类型Optional & Equ

        本文链接:https://www.haomeiwen.com/subject/ulzdeltx.html