首先看一段代码:
import XCTest
class Hero {
var killedEnemy:[Enemy] = []
func kill(ennmy: Enemy) {
killedEnemy.append(ennmy)
}
}
class Enemy {
}
protocol InstanceEquatable: class, Equatable {}
extension InstanceEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension Enemy: InstanceEquatable {}
class Tests: XCTestCase {
override func setUp() {
}
override func tearDown() {
}
func testDestoryingPerson() {
let hero = Hero()
let enemy1 = Enemy()
let enemy2 = Enemy()
let enemy3 = Enemy()
hero.kill(ennmy: enemy1)
XCTAssertTrue(hero.killedEnemy.contains(enemy2))
}
}
这段代码在说什么
在代码中,我们看到 Enemy 通过 InstanceEquatable 拓展遵循了 Equatable 协议并重载了 == 运算符。
声明了只有内存地址相等的状态下才符合 == 的定义。
此时 == 与 === 的含义相同。
在这个前提下,调用 contains 函数的含义就变成了判断 hero 干掉的 ememy 中,是否包含目标对象引用的内存地址,而不是与目标对象内容相同的实例。
=== 和 == 的区别
简单来说,Swift 中提供了两种用于判等的操作符,一个是 == ,一个是 ===
== 通常是用于判定两个对象的内容是否相同 === 通常是用于判定两个对象引用的是否为同一块内存地址。
深入一下
对于类类型来说,会存在多个实例指向同一个内存地址的情况,这是由于类类型本身是引用类型的缘故,类引用保存在 RTS (Run Time Stack) 上,而它们的实例保存在内存的堆上。
当我们使用 == 时,我们只是想验证两个实例是否相同,而不是验证两个实例是同一个实例(地址是否相同)。
此时我们就需要提供一个验证两个实例相同的规则。
通常状态下,自定义类和结构体是没有默认的 == 和 != 行为,我们需要让这些类型遵守 Equatable 协议并重载 static func == (lhs:, rhs:) -> Bool 函数,举个例子,我们给敌人加上名字和ID,像这样:
class Enemy: Equatable {
var id: Int = 0
var name = ""
init(id: Int, name: String) {
self.id=id
self.name = name
}
//不同的ID可以有相同的名字,所以我们判断是否是同一个敌人应该用ID去判断,对吧?
static func == (lhs: Enemy, rhs: Enemy) -> Bool {
return lhs.id == rhs.id
}
}
//我们多创建几个敌人
let e1 = Enemy(id: 1, name: "B")
let e2 = Enemy(id: 1, name: "B")
let e3 = e1
//来比较一下吧,是否是同一个敌人:
func compare() {
if e1 == e2 {
print("the two Enemy are equal!")
//很明显,虽然e1和e2是两个对象,但是由于他们的ID相同,所以我们认为他们是同一个敌人
}
if e1 === e2 {
} else {
//而===是检查的是引用的内存地址是否相同。由于 person1 和 person2 是完全 2 个独立构造的实例,所以它们在堆上的地址,也就是内存地址是不一样的,所以会走这里
print("the two Enemy are not identical!")
}
if e1 === e3 {
//正如我们所说的那样,类类型是引用类型,上面这段代码将 e1 的引用赋值给了 e3,现在它们同时指向 e1 指向的内存地址。所以会走这里
print("the two Enemy are identical!")
} else {
}
}