Swift 自定义结构体和类应该遵循的通用协议

Equatable & Hashable

使用自定义类型对程序中的数据进行建模时,可能经常需要检查两个值是否相同或不同,或者值列表中是否包含特定值。 此功能以及将值存储在集合中或用作字典中的键的功能受两个相关的标准库协议Equatable和Hashable约束。

你可以使用等于(==)和非等于(!=)运算符比较等于类型的实例。

可散列类型的实例可以在数学上将其值减小为单个整数,集合和字典在内部使用该整数来快速一致地进行查找。

许多标准库类型都是可等于和可哈希的,包括字符串,整数,浮点值,布尔值以及可等于和可哈希的类型的集合。 在下面的示例中,==比较和contains(_ :)方法调用取决于相等的字符串和整数:

if username == "Arturo" {
    print("Hi, Arturo!")
}

let favoriteNumbers = [4, 7, 8, 9]
if favoriteNumbers.contains(todaysDate.day) {
    print("It's a good day today!")
}

遵循Equatable和Hashable协议非常简单,可以轻松地在Swift中使用您自己的类型。 对于所有自定义模型类型而言,这都是一个好主意。

自动遵循Equatable和Hashable

您可以通过简单地在与类型的原始声明相同的文件中声明这些协议一致性,来使许多自定义类型可等于和可哈希化。 在声明类型时,将Equatable和Hashable添加到采用的协议列表中,然后编译器自动填写这两个协议的要求:

/// A position in an x-y coordinate system.
struct Position: Equatable, Hashable {
    var x: Int
    var y: Int
    
    init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
    }
}

使用Equatable一致性,您可以将等于运算符(==)或不等于运算符(!=)与Position类型的任意两个实例一起使用。

let availablePositions = [Position(0, 0), Position(0, 1), Position(1, 0)]
let gemPosition = Position(1, 0)

for position in availablePositions {
    if gemPosition == position {
        print("Gem found at (\(position.x), \(position.y))!")
    } else {
        print("No gem at (\(position.x), \(position.y))")
    }
}
// No gem at (0, 0)
// No gem at (0, 1)
// Gem found at (1, 0)!

可散列的一致性意味着您可以将职位存储在集合中,并快速检查您之前是否访问过职位,如以下示例所示:

var visitedPositions: Set = [Position(0, 0), Position(1, 0)]
let currentPosition = Position(1, 3)

if visitedPositions.contains(currentPosition) {
    print("Already visited (\(currentPosition.x), \(currentPosition.y))")
} else {
    print("First time at (\(currentPosition.x), \(currentPosition.y))")
    visitedPositions.insert(currentPosition)
}
// First time at (1, 3)

除了简化代码外,这种自动一致性还减少了错误,因为在哈希和测试相等性时会自动包括添加到自定义类型的任何新属性。 如果类型是满足以下条件的结构或枚举,则该类型可自动符合Equatable和Hashable:

对于结构,其所有存储的属性必须符合Equatable和Hashable。

对于枚举,其所有关联值必须符合Equatable和Hashable。 (没有关联值的枚举即使没有声明采用也具有Equatable和Hashable一致性。)

手动遵循 Equatable & Hashable

在以下情况下,您需要为类型手动实现Equatable和Hashable一致性:

该类型不符合上一节中列出的条件。

您要自定义类型的一致性。

您要扩展在另一个文件或模块中声明的类型以使其符合标准。

class Player {
    var name: String
    var position: Position
    
    init(name: String, position: Position) {
        self.name = name
        self.position = position
    }
}

Player类型是一个类,因此不符合自动合成Equatable或Hashable要求的条件。 要使此类符合Equatable协议,请在扩展中声明符合性并实现静态== 运算符方法。 比较 ==方法实现中每个重要属性的相等性:

extension Player: Equatable {
    static func ==(lhs: Player, rhs: Player) -> Bool {
        return lhs.name == rhs.name && lhs.position == rhs.position
    }
}

为了使Player符合Hashable协议,请在另一个扩展中声明符合性并实现hash(into :)方法。 在hash(into :)方法中,对提供的具有每个重要属性的哈希器调用Combine(_ :)方法:

extension Player: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(position)
    }
}

将所有重要属性用于Equatable&Hashable

在实现==方法和hash(into :)方法时,请使用所有会影响自定义类型的两个实例是否相等的属性。 在上面的实现中,Player类型在两种方法中都使用名称和位置。

如果您的类型包含不会影响两个实例是否相等的属性,请在==方法中进行比较,并从hash(into :)中的哈希中排除这些属性。 例如,一种类型可能会缓存一个昂贵的计算值,因此它只需要计算一次即可。 如果比较该类型的两个实例,则是否已缓存计算值不会影响它们的相等性,因此应将缓存值排除在比较和哈希之外。

在==和hash(into :)方法中始终使用相同的属性。 在集合和字典中使用自定义类型时,在两种方法中使用不同的属性组可能导致意外的行为或性能。

自定义NSObject子类行为

NSObject子类继承对Equatable和Hashable协议的一致性,并基于实例身份进行相等性处理。 如果需要自定义此行为,请重写isEqual(_ :)方法和hash属性,而不要使用==运算符方法和hashValue属性。

extension MyNSObjectSubclass {
    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? MyNSObjectSubclass
            else { return false }
        return self.firstProperty == other.firstProperty 
            && self.secondProperty == other.secondProperty
    }

    override var hash: Int {
        var hasher = Hasher()
        hasher.combine(firstProperty)
        hasher.combine(secondProperty)
        return hasher.finalize()
    }
}

如前一节所述,被认为相等的两个实例必须具有相同的哈希值。 如果您覆盖这些声明之一,则也必须覆盖另一个声明以维持该保证。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值