使 swift 协议符合 Hashable

Make a swift protocol conform to Hashable

我正在兜圈子试图让 Hashable 与符合相同 protocol.

的多个 struct 一起工作

我有一个这样声明的协议 SomeLocation

protocol SomeLocation {
    var name:String { get }
    var coordinates:Coordinate { get }
}

然后我创建多个包含类似数据的对象,如下所示:

struct ShopLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate

    init(from decoder: Decoder) throws {
        ...
    }
}

struct CarLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate

    init(from decoder: Decoder) throws {
        ...
    }
}

稍后我可以通过声明在同一个数组中使用它们:

let locations: [SomeLocation]

问题是,我创建了一个 MKAnnotation subclass 并且需要在 SomeLocation 对象上使用自定义 Hashable

final class LocationAnnotation:NSObject, MKAnnotation {
    let location:SomeLocation
    init(location:SomeLocation) {
        self.location = location
        super.init()
    }
}

override var hash: Int {
    return location.hashValue
}

override func isEqual(_ object: Any?) -> Bool {
    if let annot = object as? LocationAnnotation
    {
        let isEqual = (annot.location == location)
        return isEqual
    }
    return false
}

这给了我 2 个错误:

Value of type 'SomeLocation' has no member 'hashValue' Binary operator

'==' cannot be applied to two 'SomeLocation' operands

所以我将 Hashable 协议添加到我的 SomeLocation 协议中:

protocol SomeLocation: Hashable {
    ...
}

这消除了 hashValue 不可用的第一个错误,但现在我在声明 let location:SomeLocation

时收到错误

Protocol 'SomeLocation' can only be used as a generic constraint because it has Self or associated type requirements

看来我无法将 Hashable 添加到协议中。

我可以将 Hashable 直接添加到每个实现 SomeLocation 协议的结构,但这意味着我需要使用这样的代码并在每次我可能创建另一个符合的对象时不断更新它SomeLocation 协议。

override var hash: Int {
    if let location = location as? ShopLocation
    {
        return location.hashValue
    }
    return self.hashValue
}

我尝试了另一种方法,通过制作 SomeLocationRepresentable 结构:

struct SomeLocationRepresentable {
    private let wrapped: SomeLocation
    init<T:SomeLocation>(with:T) {
        wrapped = with
    }
}
extension SomeLocationRepresentable: SomeLocation, Hashable {
    var name: String {
        wrapped.name
    }
    
    var coordinates: Coordinate {
        wrapped.coordinates
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(coordinates)
    }

    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.name == rhs.name && lhs.coordinates == rhs.coordinates
    }
}

但是当我尝试在 LocationAnnotation class 中使用它时

let location: SomeLocationRepresentable
init(location:SomeLocation) {
    self.location = SomeLocationRepresentable(with: location)
    super.init()
}

我收到一个错误

Value of protocol type 'SomeLocation' cannot conform to 'SomeLocation'; only struct/enum/class types can conform to protocols

是否有可能实现我想要做的事情?使用所有符合协议的对象并使用自定义 Hashable 将一个与另一个进行比较?

Hashable 中导出协议并使用类型橡皮擦可能在此处有所帮助:

protocol SomeLocation: Hashable {
    var name: String { get }
    var coordinates: Coordinate { get }
}

struct AnyLocation: SomeLocation {
    let name: String
    let coordinates: Coordinate
    
    init<L: SomeLocation>(_ location: L) {
        name = location.name
        coordinates = location.coordinates
    }
}

然后你可以简单地在结构上声明协议一致性,如果Coordinate已经是Hashable,那么你不需要编写任何额外的哈希代码代码,因为编译器可以自动为你合成(只要新类型的所有属性都是 Hashable:

struct ShopLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate
}

struct CarLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate
}

如果Coordinate也是Codable,那么您也可以省略为encoding/decoding操作编写任何代码,编译将合成所需的方法(前提是所有其他属性已经Codable).

然后您可以通过转发初始化器约束在注释 class 中使用橡皮擦:

final class LocationAnnotation: NSObject, MKAnnotation {   
    let location: AnyLocation
    
    init<L: SomeLocation>(location: L) {
        self.location = AnyLocation(location)
        super.init()
    }
    
    override var hash: Int {
        location.hashValue
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        (object as? LocationAnnotation)?.location == location
    }
}