Make a swift protocol conform to Hashable

Dar*_*ren 6 protocols ios hashable swift swift-protocols

I'm going around in circles trying to get Hashable to work with multiple struct that conform to the same protocol.

I have a protocol SomeLocation declared like this:

protocol SomeLocation {
    var name:String { get }
    var coordinates:Coordinate { get }
}
Run Code Online (Sandbox Code Playgroud)

Then I create multiple objects that contain similar data like this:

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 {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

I can later use these in the same array by declaring:

let locations: [SomeLocation]
Run Code Online (Sandbox Code Playgroud)

The problem is, I create an MKAnnotation subclass and need to use a custom Hashable on the SomeLocation objects.

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
}
Run Code Online (Sandbox Code Playgroud)

This gives me 2 errors:

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

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

So I add the Hashable protocol to my SomeLocation protocol:

protocol SomeLocation: Hashable {
    ...
}
Run Code Online (Sandbox Code Playgroud)

This removes the first error of hashValue not being available, but now I get an error where I declared let location:SomeLocation saying

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

So it doesn't look like I can add Hashable to the protocol.

I can add Hashable directly to each struct that implements the SomeLocation protocol, however that means I need to use code like this and keep updating it every time I might make another object that conforms to the SomeLocation protocol.

override var hash: Int {
    if let location = location as? ShopLocation
    {
        return location.hashValue
    }
    return self.hashValue
}
Run Code Online (Sandbox Code Playgroud)

I have tried another way, by making a SomeLocationRepresentable struct:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

however when I try to use this in the LocationAnnotation class like

let location: SomeLocationRepresentable
init(location:SomeLocation) {
    self.location = SomeLocationRepresentable(with: location)
    super.init()
}
Run Code Online (Sandbox Code Playgroud)

I get an error

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

Is it possible to achieve what I am trying to do? Use objects that all conform to a protocol and use a custom Hashable to compare one to the other?

Cri*_*tik 11

从类型擦除器派生协议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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以简单地声明结构上的协议一致性,如果Coordinate已经是Hashable,那么您不需要编写任何额外的哈希代码代码,因为编译器可以自动为您合成(只要满足以下条件,编译器就会自动为您合成)他们的所有属性是Hashable

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

struct CarLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate
}
Run Code Online (Sandbox Code Playgroud)

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

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)