Faça um protocolo rápido em conformidade com Hashable
Estou andando em círculos tentando Hashabletrabalhar com múltiplos structque se conformam ao mesmo protocol.
Tenho um protocolo SomeLocationdeclarado assim:
protocol SomeLocation {
var name:String { get }
var coordinates:Coordinate { get }
}
Em seguida, crio vários objetos que contêm dados semelhantes, como este:
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 {
...
}
}
Posso usá-los posteriormente na mesma matriz, declarando:
let locations: [SomeLocation]
O problema é que eu crio uma MKAnnotationsubclasse e preciso usar um custom Hashablenos SomeLocationobjetos.
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
}
Isso me dá 2 erros:
O valor do tipo 'SomeLocation' não tem membro 'hashValue' Operador binário
'==' não pode ser aplicado a dois operandos 'SomeLocation'
Então, adiciono o Hashableprotocolo ao meu SomeLocationprotocolo:
protocol SomeLocation: Hashable {
...
}
Isso remove o primeiro erro de hashValue não estar disponível, mas agora recebo um erro onde declarei let location:SomeLocationdizendo
O protocolo 'SomeLocation' só pode ser usado como uma restrição genérica porque tem requisitos próprios ou de tipo associado
Portanto, não parece que posso adicionar algo Hashableao protocolo.
Posso adicionar Hashablediretamente a cada estrutura que implementa o SomeLocationprotocolo, no entanto, isso significa que preciso usar um código como este e continuar atualizando-o sempre que fizer outro objeto que esteja em conformidade com o SomeLocationprotocolo.
override var hash: Int {
if let location = location as? ShopLocation
{
return location.hashValue
}
return self.hashValue
}
Tentei outra maneira, criando uma SomeLocationRepresentableestrutura:
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
}
}
no entanto, quando tento usar isso na LocationAnnotationaula, como
let location: SomeLocationRepresentable
init(location:SomeLocation) {
self.location = SomeLocationRepresentable(with: location)
super.init()
}
Eu recebo um erro
O valor do tipo de protocolo 'SomeLocation' não pode estar em conformidade com 'SomeLocation'; apenas os tipos struct / enum / class podem estar em conformidade com os protocolos
É possível alcançar o que estou tentando fazer? Usar objetos que estejam todos em conformidade com um protocolo e usar um costume Hashablepara comparar um ao outro?
Respostas
Derivar o protocolo de Hashablee usar um apagador pode ajudar aqui:
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
}
}
Você pode então simplesmente declarar a conformidade do protocolo nas estruturas, e se Coordinatejá estiver Hashable, então você não precisa escrever nenhum código de hash extra, uma vez que o compilador pode sintetizar automaticamente para você (e assim o fará para novos tipos, desde que todas as suas propriedades são Hashable:
struct ShopLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
struct CarLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
Se Coordinatetambém for Codable, então você também pode omitir a escrita de qualquer código para as operações de codificação / decodificação, a compilação irá sintetizar os métodos necessários (desde que todas as outras propriedades já estejam Codable).
Você pode então usar a borracha dentro da classe de anotação encaminhando as restrições do inicializador:
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
}
}