Faça um protocolo rápido em conformidade com Hashable

Oct 22 2020

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

1 Cristik Oct 22 2020 at 13:35

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
    }
}