Swift Protocol Implements Equatable

Swift Protocol Implements Equatable


1) Allow two Cacheables of the same type to be compare

protocol Cacheable: Equatable {
//....//
func identifier() -> String
}

func ==<T : Cacheable>(lhs: T, rhs: T) -> Bool {
return lhs.identifier() == rhs.identifier()
}

Pros

This is the simplest solution.

Cons

You can only compare two Cacheable objects of the same type. This means that code below will fail and in order to fix it you need to make Animal conform to Cacheable:

class Animal {

}

class Dog: Animal,Cacheable {
func identifier() -> String {
return "object"
}
}

class Cat: Animal,Cacheable {
func identifier() -> String {
return "object"
}
}

let a = Dog()

let b = Cat()

a == b //such comparison is not allowed

2) Allow Cacheables of any type to be compared

protocol Cacheable:Equatable {
//....//
func identifier() -> String
}

func ==<T:Cacheable>(lhs: T, rhs: T) -> Bool {
return lhs.identifier() == rhs.identifier()
}

func !=<T:Cacheable>(lhs: T, rhs: T) -> Bool {
return lhs.identifier() != rhs.identifier()
}

func ==<T:Cacheable, U:Cacheable>(lhs: T, rhs: U) -> Bool {
return lhs.identifier() == rhs.identifier()
}

func !=<T:Cacheable, U:Cacheable>(lhs: T, rhs: U) -> Bool {
return lhs.identifier() != rhs.identifier()
}

Pros

Removes limitations described above for solution 1. Now you can easily compare Dog and Cat.

Cons

  • Implementation is longer. Actually I am not sure why specifying only == functions is not sufficient - this might be a bug with a compiler. Anyway, you have to provide the implementation for both == and !=.
  • In some cases the benefit of this implementation may also pose a problem as you are allowing the comparison between absolutely different objects and compiler is totally OK with it.

3) Without conforming to Equatable

protocol Cacheable {
//....//
func identifier() -> String
}

func ==(lhs: Cacheable, rhs: Cacheable) -> Bool {
return lhs.identifier() == rhs.identifier()
}

func !=(lhs: Cacheable, rhs: Cacheable) -> Bool {
return lhs.identifier() != rhs.identifier()
}

Pros

You can use Cacheable as type without needing any generics. This introduces a whole new range of possibilities. For example:

let c:[Cacheable] = [Dog(),RaceCar()]

c[0] == c[1]
c[0] != c[1]

With solutions 1 and 2 such code would fail and you would have to use generics in your classes. However, with the latest implementation Cacheable is treated as a type, so you are allowed to declare an array of type [Cacheable].

Cons

You no longer declare conformance to Equatable so any functions which accept Equatable parameters will not accept Cacheable. Obviously, apart from == and != as we declared them for Cacheables.

If this is not a problem in your code I would actually prefer this solution. Being able to treat protocol as a type is super useful in many cases.

How to implement Equatable protocol for a protocol based on the identity of two instances that implement this protocol?

There are two problems here. The first is that you can't use ObjectIdentifier on value types. So you must declare this protocol to require reference (class) types:

protocol NetworkSubscriber : class, Equatable {
func onMessage(_ packet: NetworkPacket)
}

(Please do not add a lowercase i to the beginning of protocols. This is confusing in several ways in Swift.)

Then, you cannot use this protocol as a type. It describes a type (because it relies on Self via Equatable). So functions that accept it must be generic.

func ==<T: NetworkSubscriber>(lhs: T, rhs: T) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

Given that NetworkSubscriber must be a class, you should ask very carefully whether you should be using inheritance here rather than a protocol. Protocols with associated types are very complex to use, and mixing classes and protocols creates even more complexity. Class inheritance is much simpler if you're already using classes.

Swift protocol forcing the Equatable protocol

If you're familiar with Java or C#, Swift protocols are about halfway between generics and interfaces. One thing that you can do in a protocol, for instance, is this:

protocol Foo {
func compareWith(foo: Self)
}

Classes that implement this protocol will have a method compareWith that accept an object of their own type (and not an object of type Foo).

This is what the compiler calls "Self or associated type requirements", and this is how Equatable is defined (it needs an operator== that accepts two Self operands). The downside of these protocols, though, is that you can only use them as generic constrains: you can't use them as an expression type.

The solution is to use generics. In this case, you'd make your ProtocolBuilder protocol generic, with a constraint that the type implements NameProtocol.

public protocol NameProtocol : Equatable {
var name: String { get }
}

public protocol BuilderProtocol {
typealias T: NameProtocol
func build() -> T?
init()
}

Make Swift protocol conform to Equatable on associated type

Add -> Bool. Just a bad error message. Sometimes writing function declaration across multiple lines doesn't make it more readable.

public func ==<Node1 : GraphNode, Node2 : GraphNode where Node1.Content == Node2.Content>(lhs: Node1, rhs: Node2) -> Bool {

return (lhs.content == rhs.content)

}

How to properly implement the Equatable protocol in a class hierarchy?

After lots of research and some trial and error I finally came up with a working solution. The first step was moving the == operator from inside the class to the global scope. This fixed the errors about static and final.

For the base class this became:

func == (lhs: Base, rhs: Base) -> Bool {
return lhs.x == rhs.x
}

class Base : Equatable {
var x : Int
}

And for the subclass:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
return true
}

class Subclass : Base {
var y : String
}

Now the only part left is figuring out how to call the == operator of the base class from the == operator of the subclass. This led me to the final solution:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
if lhs.y == rhs.y {
if lhs as Base == rhs as Base {
return true
}
}

return false
}

That first if statement results in a call to the == operator in the base class.


The final solution:

Base.swift:

func == (lhs: Base, rhs: Base) -> Bool {
return lhs.x == rhs.x
}

class Base : Equatable {
var x : Int
}

Subclass.swift:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
if lhs.y == rhs.y {
if lhs as Base == rhs as Base {
return true
}
}

return false
}

class Subclass : Base {
var y : String
}


Related Topics



Leave a reply



Submit