我们在 Objective-C 时代,通常使用 -isEqualToString: 来在已经能确定比较对象和待比较对象都是 NSString 的时候进行字符串判等。Swift 中的 String 类型中是没有 -isEqualToString: 或者 -isEqual: 这样的方法的,因为这些毕竟是 NSObject 的东西。在 Swift 的字符串内容判等,我们简单地使用 == 操作符来进行:

let str1 = "快乐的字符串"
let str2 = "快乐的字符串"
let str3 = "开心的字符串"

str1 == str2  // true
str1 == str3  // false

在判等上 Swift 的行为和 Objective-C 有着巨大的差别。在 Objective-C 中 == 这个符号的意思是判断两个对象是否指向同一块内存地址。其实很多时候这并不是我们经常所期望的判等,我们更关心的往往还是对象的内容相同,而这种意义的相等即使两个对象引用的不是同一块内存地址时,也是可以做到的。Objective-C 中我们通常通过对 -isEqual: 进行重写,或者更进一步去实现类似 -isEqualToString: 这样的 -isEqualToClass: 的带有类型信息的方法来进行内容判等。如果我们没有在任意子类重写 -isEqual: 的话,在调用这个方法时会直接使用 NSObject 中的版本,去直接进行 Objective-C 的 == 判断。

在 Swift 中情况大不一样,Swift 里的 == 是一个操作符的声明,在 Equatable 里声明了这个操作符的接口方法:

protocol Equatable {
    func ==(lhs: Self, rhs: Self) -> Bool
}

实现这个接口的类型需要定义适合自己类型的 == 操作符,如果我们认为两个输入有相等关系的话,就应该返回 true。实现了 Equatable 的类型就可以使用 == 以及 != 操作符来进行相等判定了 (在实现时我们只需要实现 ==!= 的话由标准库自动取反实现)。这和原来 Objective-C 的 isEqual: 的行为十分相似。比如我们在一个待办事项应用中,从数据库中取得带有使用 uuid 进行编号的待办条目,在实践中我们一般考虑就使用这个 uuid 来判定两个条目对象是不是同一条目。让这个表示条目的 TodoItem 类实现 Equatable 接口:

class TodoItem {
    let uuid: String
    var title: String

    init(uuid: String, title: String) {
        self.uuid = uuid
        self.title = title
    }
}

extension TodoItem: Equatable {

}

func ==(lhs: TodoItem, rhs: TodoItem) -> Bool {
    return lhs.uuid == rhs.uuid
}

对于 == 的实现我们并没有像实现其他一些接口一样将其放在对应的 extension 里,而是放在了全局的 scope 中。这是合理的做法,因为你应该需要在全局范围内都能使用 ==。事实上,Swift 的操作符都是全局的,关于操作符的更多信息,可以参看操作符一节。

Swift 的基本类型都重载了自己对应版本的 ==,而对于 NSObject 的子类来说,如果我们使用 == 并且没有对于这个子类的重载的话,将转为调用这个类的 -isEqual: 方法。这样如果这个 NSObject 子类原来就实现了 -isEqual: 的话,直接使用 == 并不会造成它和 Swift 类型的行为差异;但是如果无法找到合适的重写的话,这个方法就将回滚到最初的 NSObject 里的实现,对引用对象地址进行直接比较。因此对于 NSObject 子类的判等你有两种选择,要么重载 ==,要么重写 -isEqual:。如果你只在 Swift 中使用你的类的话,两种方式是等效的;但是如果你还需要在 Objective-C 中使用这个类的话,因为 Objective-C 不接受操作符重载,只能使用 -isEqual:,这时你应该考虑使用第二种方式。

对于原来 Objective-C 中使用 == 进行的对象指针的判定,在 Swift 中提供的是另一个操作符 ===。在 Swift 中 === 只有一种重载:

func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool

它用来判断两个 AnyObject 是否是同一个引用。

对于判等,和它紧密相关的一个话题就是哈希,因为哈希是一个稍微复杂的话题,所以我将它分成了一个单节。但是如果在实际项目中你需要重载 == 或者重写 -isEqual: 来进行判等的话,很可能你也会想看看有关哈希的内容,重载了判等的话,我们还需要提供一个可靠的哈希算法使得判等的对象在字典中作为 key 时不会发生奇怪的事情。