如何使用Xcode 10中可用的API使枚举符合Hashable协议?

17

在我Swift 4.2.1的代码中,我有这个枚举:

enum MyEnum {

    case caseOne(Int)
    case caseTwo(String)
    case caseThree
}

它符合Equatable

extension MyEnum: Equatable {

    static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {

        switch (lhs, rhs) {
        case (.caseOne, .caseOne), (.caseTwo, .caseTwo), (.caseThree, .caseThree):
            return true
        default:
            return false
        }
    }
}

我需要让它符合Hashable协议,因此我添加了一个扩展(extension):

extension MyEnum: Hashable {

    var hashValue: Int {

        switch self {
        case .caseOne:
            return 1
        case .caseTwo:
            return 2
        case .caseThree:
            return 3
        }
    }
}

现在我想迁移到Xcode 10中可用的新API。我删除了我的hashValue实现并添加了hash(into:)实现:

extension MyEnum: Hashable {

    func hash(into hasher: inout Hasher) {

        switch self {
        case .caseOne:
            hasher.combine(1)
        case .caseTwo:
            hasher.combine(2)
        case .caseThree:
            hasher.combine(3)
        }
    }
}

请问我是否成功地切换到了新的API?我使用这个测试,如果一切正常,它会打印出true两次:

var testDictionary = [MyEnum: Int]()
testDictionary[.caseOne(100)] = 100
testDictionary[.caseOne(1000)] = 1000
testDictionary[.caseTwo("100")] = 100
testDictionary[.caseTwo("1000")] = 1000
let countCaseOne = testDictionary.reduce(0) {
    if case .caseOne = $1.key {
        return $0 + 1
    }
    return $0
} == 1
print(countCaseOne) // true
let countCaseTwo = testDictionary.reduce(0) {
    if case .caseTwo = $1.key {
        return $0 + 1
    }
    return $0
} == 1
print(countCaseTwo) // true

你想知道什么?在我看来,它是正确的。 - Sandeep
@Sandeep 在教程中,我看到了类似于hasher.combine(self.property)的内容在hash(into:)的实现中。但是我没有找到枚举的例子。这就是为什么我想知道我的实现是否正确的原因。 - Roman Podymov
2
顺便提一下,你不必亲自实现这个方法。语言会根据你的属性自动完成它。 - Sandeep
2个回答

20

您可以使用其他答案中提出的自动生成的Hashable符合条件(前提是您的类型不包含任何非Hashable类型的数据)。

但这是在一般情况下可以做的(自动生成的代码可能也是这样的):

extension MyEnum: Hashable {

    func hash(into hasher: inout Hasher) {

        switch self {
        case .caseOne(let value):
            hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
        case .caseTwo(let value):
            hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
        case .caseThree:
            // you can `combine` with some `Hashable` constant, but here it's ok just to skip
            break
        }
    }
}

谢谢您的回答。但是为什么我可以在 .caseThree 中跳过 combine - Roman Podymov
3
因为 hasher 已经有一些初始状态(我们称之为 seed),然后 combine(:) 会对 hasher 状态(最初的 seed)和组合值执行某些函数,并用结果替换 hasher 的状态。然后 hasher 的状态会转换为 hash 函数的结果,可以通过 hashValue 属性访问。因此,即使您完全不使用 combine,仍然会得到一个结果。当然,如果您有多个没有关联值的 case,最好将其与某个 constant 结合起来,否则这些 case 的哈希值将相同。 - user28434'mstep
请检查我的编辑答案,我给 MyEnum 添加了 == - Roman Podymov
因为我不需要检查相关的值。 - Roman Podymov
1
@RomanPodymov,如果您不需要用于哈希,那么使用答案中的hash(into:)实现就可以了。 - user28434'mstep
显示剩余2条评论

17

无需手动实现Hashable协议,编译器可以自动为您的特定enum合成该协议(其中所有带有关联值的情况都具有Hashable关联值)。 您只需要声明符合性即可。

enum MyEnum: Hashable {
    case caseOne(Int)
    case caseTwo(String)
    case caseThree
}

// You don't even need to write `: Equatable`, since automatic Hashable conformance takes care of Equatable too, I just left it there for clarity
extension MyEnum: Equatable {
    static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
        switch (lhs, rhs) {
        case (.caseOne, .caseOne), (.caseTwo, .caseTwo), (.caseThree, .caseThree):
            return true
        default:
            return false
        }
    }
}

3
如果枚举类型的所有关联值也是“可哈希”的(或者没有关联值),则符合条件。 - user28434'mstep
1
@user28434 是的,这就是为什么我提到了针对 OP 特定枚举的情况,但我会编辑我的答案使其更加通用。 - Dávid Pásztor
谢谢你的回答。但是我忘了写MyEnum包含了==实现,请查看我的更新后的答案。 - Roman Podymov
1
@RomanPodymov,这没有任何区别,您仍然可以使用自动Hashable符合性并覆盖自动生成的==方法。请查看我的更新回答。顺便问一下,您确定不关心相关值的相等性吗? - Dávid Pásztor
在我的情况下,我不需要检查相关的值。如果没有实现hash(into:),那么我的测试输出将是true/truetrue/false或者false/true - Roman Podymov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接