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 }
}
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 {
...
}
}
I can later use these in the same array by declaring:
let locations: [SomeLocation]
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
}
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 {
...
}
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
}
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
}
}
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()
}
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?
Deriving the protocol from Hashable
and using a type eraser might help here:
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
}
}
You then can simply declare the protocol conformance on the structs, and if Coordinate
is already Hashable
, then you don't need to write any extra hashing code code, since the compiler can automatically synthesize for you (and so will do for new types as long as all their properties are Hashable
:
struct ShopLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
struct CarLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
If Coordinate
is also Codable
, then you also can omit writing any code for the encoding/decoding operations, the compile will synthesize the required methods (provided all other properties are already Codable
).
You can then use the eraser within the annotation class by forwardingn the initializer constraints:
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With