使一个快速协议符合可哈希化要求

19

我正在尝试让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子类并需要在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
}

这给了我两个错误:

类型 'SomeLocation' 的值没有成员 'hashValue' 二进制运算符

'==' 不能应用于两个 'SomeLocation' 操作数

因此,我将 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类中使用时,出现了问题。
let location: SomeLocationRepresentable
init(location:SomeLocation) {
    self.location = SomeLocationRepresentable(with: location)
    super.init()
}

我遇到了一个错误

协议类型“SomeLocation”的值无法符合“SomeLocation”;只有结构体/枚举/类类型才能符合协议

我正在尝试做的事情是否可行?使用所有符合某个协议的对象,并使用自定义的Hashable将其相互比较?


1
你的问题源于尝试在某些地方(例如创建数组[SomeLocation]时)将协议用作存在形式(即代替对象),同时还尝试符合Hashable,它具有Self约束,这会防止它在Swift中被用作存在形式。类型抹消(就像你使用SomeLocationRepresentable一样 - 在Swift中的惯例是将其命名为AnyLocation)是正确的方法,因此您将拥有var locations:[AnyLocation] = [AnyLocation(CarLocation())] - New Dev
不是关于你的问题,但你打算如何检查它们是否在同一位置?它们可能非常接近但并不相等。顺便说一下,它们可能共享相同的纬度/经度,但是位于不同的地方(即不同的层级)。 - Leo Dabus
谢谢 @NewDev,你有任何想法为什么我的尝试失败了吗? - Darren
2
它失败了,因为您尝试使用类型SomeLocation初始化SomeLocationRepresentable,而它期望的是符合SomeLocation的类型。协议不符合协议,包括它们自己。当您有Self约束(Hashable就是这种情况)时,您基本上不能拥有一个是SomeLocation的东西。 - New Dev
实际上不行,它无法将类型为 SomeLocation 的值转换为 SomeLocationRepresentable :/ - Darren
显示剩余4条评论
1个回答

10
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,那么您还可以省略编码/解码操作的任何代码,编译器将综合所需的方法(前提是所有其他属性已经是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
    }
}

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