美文网首页swift语法swift
Swift - Equatable & Comparable

Swift - Equatable & Comparable

作者: just东东 | 来源:发表于2021-03-12 10:08 被阅读0次

    Swift - Equatable & Comparable

    [TOC]

    前言

    Swift 提供了很对运算符,详情请参考基本运算符

    在Swift中,或者其他编程语言中,我们都会通过比较运算符来比较左右两个值。下面我们就来探索一下Swift中这些运算符是怎么实现的。

    1. Equatable

    1.1 初探

    首先我们来看个例子:

    var a: Int? = 10
    var b:Optional<Int> = 20
    
    if a == b {
        print("a == b")
    }
    

    这里面两个可选类型之间可以通过==去判断是否相等,我们就去Optional源码中看看:

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

    我们可以看到,在源码中Optional遵守了Equatable协议,在实现了==方法,内部通过模式匹配判断两个值相等或者都为nil的时候返回true,否则返回false

    对于基本类型也可以实现比较:

    var a: Int = 10
    var b: Int = 20
    
    print(a == b)
    
    <!--打印结果-->
    false
    

    其实Swift中的类型,可以通过遵守Equatable协议来使用相等运算符==和不等运算符!=来比较两个值相等还是不相等。

    Swift标准库中绝大多数的类型都默认实现了Swift.Equatable协议。比如我们熟知的String

    extension String : Swift.Equatable {
    @inlinable @inline(__always) @_effects(readonly) @_semantics("string.equals") public static func == (lhs: Swift.String, rhs: Swift.String) -> Swift.Bool {
        return _stringCompare(lhs._guts, rhs._guts, expecting: .equal)
      }
    }
    

    对于Int,在Swift源码中可以看到它是通过标准模块Builtin中中的比较方法cmp_eq_Int64来实现的。

    @_transparent public static func == (lhs: Swift.Int, rhs: Swift.Int) -> Swift.Bool {
        return Bool(Builtin.cmp_eq_Int64(lhs._value, rhs._value))
    }
    

    对于其他基本类型的的实现也都大同小异,感兴趣的可以去源码中查看查看,我这里的源码版本是Swift5.3.1

    注意:
    根据官方文档中的说明:对于元组类型的比较,Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
    还有一点需要注意,Bool类型只能比较相等和不等,不能比较大小等

    1.2 自定义类型实现==

    如果我们对于自定义的类型实现==,这个时候我们应该怎么做呢?

    1.2.1 struct

    如果什么都不做,直接写就会报编译错误:

    image

    此时给我们的Person遵守上Equatable协议就不会报错了。

    struct Person: Equatable {
        var name: String
        var age: Int
    }
    
    let p = Person(name: "xiaohei", age: 18)
    let p1 = Person(name: "xiaohei", age: 18)
    
    if p == p1 {
        print("It's the same person")
    }
    
    <!--打印结果-->
    It's the same person
    
    // 如果将上述实例中的任一属性值修改后则就不会打印上面的结果了
    

    关于上面的打印结果的原因是因为我们遵守了Equatable协议,系统默认帮我们实现了==方法,这点可以通过sil代码进行查看:

    image

    我们可以看到这个struct默认有一个==方法,对应的名称是__derived_struct_equals,下面我们就找到这个方法看看其内部是如何实现的。

    image

    我们可以看到默认的内部实现是分别比较结构体中的值,如果都相等则返回true,有一个不同则返回false

    当然我们也可以自定义实现,我们认为两个人名字相同即是同一个人:

    struct Person: Equatable {
        var name: String
        var age: Int
        
        static func == (lhs: Self, rhs: Self) -> Bool {
            return lhs.name == rhs.name
        }
    }
    
    let p = Person(name: "xiaohei", age: 18)
    let p1 = Person(name: "xiaohei", age: 19)
    
    if p == p1 {
        print("It's the same person")
    }
    
    <!--打印结果-->
    It's the same person
    

    1.2.2 class

    class中如果不遵守Equatable协议会和struct一样报编译错误:

    image

    当我们遵守Equatable协议后,会提示我们去实现协议中的==方法

    image

    实现后如下:

    class Person: Equatable {
        
        var name: String
        var age: Int
        
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
        
        static func == (lhs: Person, rhs: Person) -> Bool {
            return lhs.name == rhs.name && lhs.age == rhs.age
        }
    }
    
    let p = Person(name: "xiaohei", age: 18)
    let p1 = Person(name: "xiaohei", age: 18)
    
    if p == p1 {
        print("It's the same person")
    }
    
    <!--打印结果-->
    It's the same person
    

    当然,我们自己实现==方法就可以按照我们的需求去返回里面的结果,这里判断了nameage,在开发中我们可能更注重判断uuid,但是这里没有。这里就给了开发者选择的空间。

    这里还有多说一点,如果我们的属性都是可选类型的:

    class Person: Equatable {
        
        var name: String?
        var age: Int?
        
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
        
        static func == (lhs: Person, rhs: Person) -> Bool {
            return lhs.name == rhs.name && lhs.age == rhs.age
        }
    }
    
    let p = Person(name: "xiaohei", age: 18)
    let p1 = Person(name: "xiaohei", age: 19)
    
    if p == p1 {
        print("It's the same person")
    }
    

    生成sil代码:

    image

    我们可以通过sil代码看见,对于可选类型,其比较是通过可选类型的==进行比较的。

    1.3 ===

    Swift 也提供恒等(===)和不恒等(!==)这两个比较符来判断两个对象是否引用同一个对象实例。

    所以我们要区分=====

    • ==相当于equal to,用于判断两个值是否相等
    • ==用于判断两个对象是否引用同一个对象实例
    • !=!==也与上述一致

    1.4 源码

    1.4.1 ==和!=

    public protocol Equatable {
      static func == (lhs: Self, rhs: Self) -> Bool
    }
    
    extension Equatable {
      @_transparent
      public static func != (lhs: Self, rhs: Self) -> Bool {
        return !(lhs == rhs)
      }
    }
    

    我们可以看到Equatable是一个协议,那些需要遵守Equatable协议的可以自己实现内部的内容。如果实现了==后面给了默认实现!=,就是对==的结果取反,这点设计很精妙。

    1.4.2 === 和 !==

    关于===!==的源码实现这里是两个全局函数。

    ===内部是比较左右两的对象的唯一标识,即通过ObjectIdentifier或取到的唯一标识,如果相同或者都为空则返回true,否则返回false

    !==是对===的结果取反。

    @inlinable // trivial-implementation
    public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
      switch (lhs, rhs) {
      case let (l?, r?):
        return ObjectIdentifier(l) == ObjectIdentifier(r)
      case (nil, nil):
        return true
      default:
        return false
      }
    }
    
    @inlinable // trivial-implementation
    public func !== (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
      return !(lhs === rhs)
    }
    

    2. Comparable

    除了Equatable还有Comparable,其中定义了< 、<=、>=、>四个比较运算符,以及扩展了...、..<区间运算符。

    2.1 源码

    这次我们先看看源码:

    以下代码在Comparable.swift文件中,看了其扩展实现,只能用一个妙字来形容,此处我们只需自己实现一个<比较运算符即可实现全部比较运算:

    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 {
    
      @inlinable
      public static func > (lhs: Self, rhs: Self) -> Bool {
        return rhs < lhs
      }
    
      @inlinable
      public static func <= (lhs: Self, rhs: Self) -> Bool {
        return !(rhs < lhs)
      }
    
      @inlinable
      public static func >= (lhs: Self, rhs: Self) -> Bool {
        return !(lhs < rhs)
      }
    }
    

    并在ClosedRange.swift文件中实现了如下代码,此处是闭区间运算符:

    extension Comparable {  
      @_transparent
      public static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self> {
        _precondition(
          minimum <= maximum, "Can't form Range with upperBound < lowerBound")
        return ClosedRange(uncheckedBounds: (lower: minimum, upper: maximum))
      }
    }
    

    Range.swift文件中实现了如下代码,此处是半开区间运算符:

      @_transparent
      public static func ..< (minimum: Self, maximum: Self) -> Range<Self> {
        _precondition(minimum <= maximum,
          "Can't form Range with upperBound < lowerBound")
        return Range(uncheckedBounds: (lower: minimum, upper: maximum))
      }
    

    2.2 使用示例

    此处我们以struct为例,重写<比较运算符

    struct Person: Comparable {
        var name: String
        var age: Int
    
        static func < (lhs: Person, rhs: Person) -> Bool {
            return lhs.age < rhs.age
        }
    }
    
    let p = Person(name: "xiaohei", age: 18)
    let p1 = Person(name: "xiaohei", age: 19)
    
    if p < p1 {
        print("p < p1")
    } else {
        print("p >= p1")
    }
    
    <!--打印结果-->
    p < p1
    

    关于<在基本类型中的实现也可以在源码中找到:

    @_transparent public static func < (lhs: Swift.Int, rhs: Swift.Int) -> Swift.Bool {
        return Bool(Builtin.cmp_slt_Int64(lhs._value, rhs._value))
    }
      
    extension String : Swift.Comparable {
      @inlinable @inline(__always) @_effects(readonly) public static func < (lhs: Swift.String, rhs: Swift.String) -> Swift.Bool {
        return _stringCompare(lhs._guts, rhs._guts, expecting: .less)
      }
    }
    

    3. 总结

    本文介绍了Swift提供给开发者的两个协议Equatable & Comparable

    1. Equatable协议中有==这个方法
    2. 通过扩展实现了中对==取反实现了!=
    3. 在其实现文件中还有两个全局方法分别实现了===!==
      1. ===比较的是两个AnyObject的通过ObjectIdentifier获取的唯一标识
      2. !==是对===结果的取反
    4. Comparable协议中有< 、<=、>=、>方法
    5. 并在扩展中首先实现了<=、>=、>方法,所以开发者只需自己实现<方法就相当于同时实现了以上4个方法
    6. Comparable协议有通过另外的扩展实现了...、..<区间运算符,具体实现详见2.1中的源码

    相关文章

      网友评论

        本文标题:Swift - Equatable & Comparable

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