Swift - Equatable & Comparable

Swift - Equatable & Comparable

[TOC]

前言

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

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

1. Equatable

1.1 初探

首先我们来看个例子:

var a: Int? = 10
var b:Optional = 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 {
    _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 {
    _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)