检查是否符合Hashable协议

3

我有一个基础协议(模型),一些结构体符合它。它们也符合可哈希化的要求。

protocol Model {}
struct Contact: Model, Hashable {
    var hashValue: Int { return ... }
    static func ==(lhs: Contact, rhs: Contact) -> Bool { return ... } 
}
struct Address: Model, Hashable {
    var hashValue: Int { return ... }
    static func ==(lhs: Address, rhs: Address) -> Bool { return ... } 
}

我有一个函数,接受符合Model([Model])的对象数组。如何将[Model]传递给需要Hashable的函数而不使Model成为Hashable?
func complete(with models: [Model]) {
    doSomethingWithHashable(models) //can't do this
}
func doSomethingWithHashable <T:Hashable>(_ objects: [T]) {
    //
}

我试图避免这种情况

protocol Model: Hashable {}
func complete<T:Model>(with models: [T]) {
    runComparison(models)
}

当我这样做时,会出现“模型无法用作通用约束”的错误提示。
protocol SomethingElse {
    var data: [Model] { get }
}

如果有另一种类型符合“Model”,但不符合“Hashable”会怎么样? - Martin R
我没有那种情况。 - joels
1
有点相关:在实现Equatable的结构体数组上进行操作 - 你可以以类似于那个问答中的AnyVehicle的方式构建一个类型擦除包装器AnyHashableModel(主要区别只是额外存储了一个hashValue的函数)。 - Hamish
应该可以了。你能把你的评论变成一个答案吗? - joels
@joels 当然没问题 :) - Hamish
1个回答

2
你的代码问题在于,你使用了Model这个术语,它并没有保证实现了Hashable协议。正如你所指出的那样,告诉编译器让Model遵循Hashable协议的问题是,你失去了以符合Model协议的异构类型为基础进行操作的能力。
如果你一开始就不关心是否符合Model协议,那么你可以使用标准库中的AnyHashable类型擦除包装器,用于完全任意的符合Hashable协议的实例。
然而,如果您确实关心模型符合性,那么您将不得不为既符合Model又符合Hashable的实例构建自己的类型擦除包装器。在我的答案中,我演示了如何为符合Equatable的类型构建类型擦除器。那里的逻辑可以很容易地扩展到Hashable——我们只需要存储一个额外的函数来返回实例的hashValue
例如:
struct AnyHashableModel : Model, Hashable {

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

        // forward to both lhs's and rhs's _isEqual in order to determine equality.
        // the reason that both must be called is to preserve symmetry for when a
        // superclass is being compared with a subclass.
        // if you know you're always working with value types, you can omit one of them.
        return lhs._isEqual(rhs) || rhs._isEqual(lhs)
    }

    private let base: Model

    private let _isEqual: (_ to: AnyHashableModel) -> Bool
    private let _hashValue: () -> Int

    init<T : Model>(_ base: T) where T : Hashable {

        self.base = base

        _isEqual = {
            // attempt to cast the passed instance to the concrete type that
            // AnyHashableModel was initialised with, returning the result of that
            // type's == implementation, or false otherwise.
            if let other = $0.base as? T {
                return base == other
            } else {
                return false
            }
        }

        // simply assign a closure that captures base and returns its hashValue
        _hashValue = { base.hashValue }
    }

    var hashValue: Int { return _hashValue() }
}

然后您可以像这样使用它:
func complete(with models: [AnyHashableModel]) {
    doSomethingWithHashable(models)
}

func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
    //
}

let models = [AnyHashableModel(Contact()), AnyHashableModel(Address())]
complete(with: models)

我假设你也想将其用作 Model 要求的包装器(如果有的话)。或者,你可以暴露 base 属性并从 AnyHashableModel 自身中删除 Model 符合性,使调用者访问基础 Model 符合实例的 base。
struct AnyHashableModel : Hashable {
    // ...
    let base: Model
    // ...
}

但是请注意,上述的类型擦除包装器仅适用于既实现了 Hashable 又实现了 Model 协议的类型。如果我们想要谈论一些其他遵循 Hashable 协议的实例,该怎么办呢?

更通用的解决方案(就像我在 这个问答 中展示的那样)是接受同时实现了 Hashable 和某个其他协议的类型——其类型由泛型占位符表示。

由于在 Swift 中目前没有办法表达一个泛型占位符必须符合由另一个泛型占位符给定的协议,这种关系必须由调用者通过一个 transform 闭包来定义以执行必要的向上转换。不过,由于 Swift 3.1 接受扩展中的具体同类型要求,我们可以为 Model 定义一个便利初始化程序来消除这个样板代码(并且这可以重复用于其他协议类型)。

例如:

/// Type-erased wrapper for a type that conforms to Hashable,
/// but inherits from/conforms to a type T that doesn't necessarily require
/// Hashable conformance. In almost all cases, T should be a protocol type.
struct AnySpecificHashable<T> : Hashable {

    static func ==(lhs: AnySpecificHashable, rhs: AnySpecificHashable) -> Bool {
        return lhs._isEqual(rhs) || rhs._isEqual(lhs)
    }

    let base: T

    private let _isEqual: (_ to: AnySpecificHashable) -> Bool
    private let _hashValue: () -> Int

    init<U : Hashable>(_ base: U, upcast: (U) -> T) {

        self.base = upcast(base)

        _isEqual = {
            if let other = $0.base as? U {
                return base == other
            } else {
                return false
            }
        }

        _hashValue = { base.hashValue }
    }
    var hashValue: Int { return _hashValue() }
}

// extension for convenience initialiser for when T is Model.
extension AnySpecificHashable where T == Model {
    init<U : Model>(_ base: U) where U : Hashable {
        self.init(base, upcast: { $0 })
    }
}

您现在需要将实例包装在一个AnySpecificHashable<Model>中:
func complete(with models: [AnySpecificHashable<Model>]) {
    doSomethingWithHashable(models)
}

func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
    //
}

let models: [AnySpecificHashable<Model>] = [
    AnySpecificHashable(Contact()),
    AnySpecificHashable(Address())
]

complete(with: models)

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