ios - Swift Equatable en un protocolo
protocols (7)
La razón por la que debería pensar dos veces antes de que un protocolo se ajuste a Equatable
es que en muchos casos simplemente no tiene sentido. Considera este ejemplo:
protocol Pet: Equatable {
var age: Int { get }
}
extension Pet {
static func == (lhs: Pet, rhs: Pet) -> Bool {
return lhs.age == rhs.age
}
}
struct Dog: Pet {
let age: Int
let favoriteFood: String
}
struct Cat: Pet {
let age: Int
let favoriteLitter: String
}
let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")
if rover == simba {
print("Should this be true??")
}
Alude a la verificación de tipos dentro de la implementación de ==
pero el problema es que no tiene información sobre ninguno de los tipos más allá de ellos como Pet
y no sabe todas las cosas que podrían ser una Pet
(tal vez agregue una Bird
y Rabbit
más tarde). Si realmente necesita esto, otro enfoque puede ser modelar cómo lenguajes como C # implementan la igualdad, haciendo algo como:
protocol IsEqual {
func isEqualTo(_ object: Any) -> Bool
}
protocol Pet: IsEqual {
var age: Int { get }
}
struct Dog: Pet {
let age: Int
let favoriteFood: String
func isEqualTo(_ object: Any) -> Bool {
guard let otherDog = object as? Dog else { return false }
return age == otherDog.age && favoriteFood == otherDog.favoriteFood
}
}
struct Cat: Pet {
let age: Int
let favoriteLitter: String
func isEqualTo(_ object: Any) -> Bool {
guard let otherCat = object as? Cat else { return false }
return age == otherCat.age && favoriteLitter == otherCat.favoriteLitter
}
}
let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")
if !rover.isEqualTo(simba) {
print("That''s more like it.")
}
En ese punto, si realmente lo deseara, podría implementar ==
sin implementar Equatable
:
static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) }
Sin embargo, una cosa que tendrías que tener en cuenta en este caso es la herencia. Debido a que podría reducir un tipo heredado y borrar la información que podría hacer que isEqualTo
no tenga sentido lógico.
Sin embargo, la mejor manera de hacerlo es implementar la igualdad en la clase / estructura y usar otro mecanismo para la verificación de tipos.
No creo que esto pueda hacerse, pero lo preguntaré de todos modos. Tengo un protocolo:
protocol X {}
Y una clase:
class Y:X {}
En el resto de mi código me refiero a todo lo que usa el protocolo X. En ese código me gustaría poder hacer algo como:
let a:X = ...
let b:X = ...
if a == b {...}
El problema es que si intento implementar Equatable
:
protocol X: Equatable {}
func ==(lhs:X, hrs:X) -> Bool {
if let l = lhs as? Y, let r = hrs as? Y {
return l.something == r.something
}
return false
}
La idea de intentar y permitir el uso de ==
mientras se ocultan las implementaciones detrás del protocolo.
Sin embargo, a Swift no le gusta esto porque Equatable
tiene referencias Equatable
y ya no me permitirá usarlo como un tipo. Sólo como un argumento genérico.
Entonces, ¿alguien ha encontrado una manera de aplicar un operador a un protocolo sin que el protocolo se vuelva inutilizable como un tipo?
No estoy seguro de por qué necesita que todas las instancias de su protocolo se ajusten a Equatable
, pero prefiero dejar que las clases implementen sus métodos de igualdad.
En este caso, dejaría el protocolo simple:
protocol MyProtocol {
func doSomething()
}
Si necesita que un objeto que se ajuste a MyProtocol
también sea Equatable
, puede usar MyProtocol & Equatable
como restricción de tipo:
// Equivalent: func doSomething<T>(element1: T, element2: T) where T: MyProtocol & Equatable {
func doSomething<T: MyProtocol & Equatable>(element1: T, element2: T) {
if element1 == element2 {
element1.doSomething()
}
}
De esta manera, puede mantener su especificación clara y dejar que las subclases implementen su método de igualdad solo si es necesario.
Si implementa directamente Equatable
en un protocolo, ya no será utilizable como un tipo, lo que anula el propósito de usar un protocolo. Incluso si solo implementa las funciones ==
en los protocolos sin la Equatable
, los resultados pueden ser erróneos. Vea esta publicación en mi blog para ver una demostración de estos temas:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/
El enfoque que he encontrado que funciona mejor es utilizar el borrado de tipo. Esto permite hacer comparaciones ==
para los tipos de protocolo (envueltos en borradores de tipo). Es importante tener en cuenta que, si bien continuamos trabajando a nivel de protocolo, las comparaciones reales ==
se delegan a los tipos concretos subyacentes para garantizar resultados correctos.
Construí un borrador de tipo usando su breve ejemplo y agregué un código de prueba al final. Agregué una constante de tipo String
al protocolo y creé dos tipos conformes (las estructuras son las más fáciles para propósitos de demostración) para poder probar los distintos escenarios.
Para obtener una explicación detallada de la metodología de borrado de tipo utilizada, consulte la segunda parte de la publicación del blog anterior:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/
El siguiente código debe ser compatible con la comparación de igualdad que desea implementar. Solo tiene que ajustar el tipo de protocolo en una instancia de borrador de tipo.
protocol X {
var name: String { get }
func isEqualTo(_ other: X) -> Bool
func asEquatable() -> AnyEquatableX
}
extension X where Self: Equatable {
func isEqualTo(_ other: X) -> Bool {
guard let otherX = other as? Self else { return false }
return self == otherX
}
func asEquatable() -> AnyEquatableX {
return AnyEquatableX(self)
}
}
struct Y: X, Equatable {
let name: String
static func ==(lhs: Y, rhs: Y) -> Bool {
return lhs.name == rhs.name
}
}
struct Z: X, Equatable {
let name: String
static func ==(lhs: Z, rhs: Z) -> Bool {
return lhs.name == rhs.name
}
}
struct AnyEquatableX: X, Equatable {
var name: String { return value.name }
init(_ value: X) { self.value = value }
private let value: X
static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool {
return lhs.value.isEqualTo(rhs.value)
}
}
// instances typed as the protocol
let y: X = Y(name: "My name")
let z: X = Z(name: "My name")
let equalY: X = Y(name: "My name")
let unequalY: X = Y(name: "Your name")
// equality tests
print(y.asEquatable() == z.asEquatable()) // prints false
print(y.asEquatable() == equalY.asEquatable()) // prints true
print(y.asEquatable() == unequalY.asEquatable()) // prints false
Tenga en cuenta que dado que el borrador de tipo se ajusta al protocolo, puede usar instancias del borrador de tipo en cualquier lugar donde se espera una instancia del tipo de protocolo.
Espero que esto ayude.
Tienes que implementar una extensión de protocolo restringida a tu tipo de clase. Dentro de esa extensión debes implementar el operador Equatable
.
public protocol Protocolable: class, Equatable
{
// Other stuff here...
}
public extension Protocolable where Self: TheClass
{
public static func ==(lhs: Self, rhs:Self) -> Bool
{
return lhs.name == rhs.name
}
}
public class TheClass: Protocolable
{
public var name: String
public init(named name: String)
{
self.name = name
}
}
let aClass: TheClass = TheClass(named: "Cars")
let otherClass: TheClass = TheClass(named: "Wall-E")
if aClass == otherClass
{
print("Equals")
}
else
{
print("Non Equals")
}
Pero permítame recomendarle que agregue la implementación del operador a su clase. Mantenlo simple ;-)
Todas las personas que dicen que no puede implementar Equatable
para un protocolo simplemente no se esfuerzan lo suficiente. Aquí está la solución (Swift 4.1 ) para su protocolo X
ejemplo:
protocol X: Equatable {
var something: Int { get }
}
// Define this operator in the global scope!
func ==<L: X, R: X>(l: L, r: R) -> Bool {
return l.something == r.something
}
¡Y funciona!
class Y: X {
var something: Int = 14
}
struct Z: X {
let something: Int = 9
}
let y = Y()
let z = Z()
print(y == z) // false
y.something = z.something
pirnt(y == z) // true
El único problema es que no puede escribir let a: X = Y()
porque el error "El protocolo solo se puede usar como una restricción genérica" .
Todavía recomendaría no implementar el uso de polimorfismo ==
. Es un poco de un olor de código. Si quiere darle al usuario del framework algo con lo que pueda probar la igualdad, entonces realmente debería vender una struct
, no un protocol
. Eso no quiere decir que no puedan ser los protocol
que están vendiendo las struct
, aunque:
struct Info: Equatable {
let a: Int
let b: String
static func == (lhs: Info, rhs: Info) -> Bool {
return lhs.a == rhs.a && lhs.b == rhs.b
}
}
protocol HasInfo {
var info: Info { get }
}
class FirstClass: HasInfo {
/* ... */
}
class SecondClass: HasInfo {
/* ... */
}
let x: HasInfo = FirstClass( /* ... */ )
let y: HasInfo = SecondClass( /* ... */ )
print(x == y) // nope
print(x.info == y.info) // yep
Creo que esto comunica de manera más eficiente su intención, que es básicamente "tiene estas cosas y no sabe si son las mismas cosas, pero sí sabe que tienen el mismo conjunto de propiedades y puede probar si esas propiedades son las mismas. mismo." Eso es bastante parecido a cómo implementaría ese ejemplo de Money
.
tal vez esto te sea útil:
protocol X:Equatable {
var name: String {get set}
}
extension X {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.name == rhs.name
}
}
struct Test : X {
var name: String
}
let first = Test(name: "Test1")
let second = Test(name: "Test2")
print(first == second) // false