rb612
rb612

Reputation: 5573

Why does the following protocol have this required function?

The following code was mentioned at WWDC 2015:

protocol Drawable {
  func isEqualTo(other: Drawable) -> Bool
  func draw()
}

extension Drawable where Self : Equatable {
  func isEqualTo(other: Drawable) -> Bool {
    if let o = other as? Self { return self == o }
    return false
  } 
}

I'm a little confused on this whole protocol extension thing. Why would they have isEqualTo(other: Drawable) -> Bool in the Drawable protocol and then only extend when self is equatable? Why should isEqualTo be a required method for all Drawable objects? From my view, if a new class/struct hasn't implemented Equatable, the objects don't have the capability to be logically checked for equality, so they couldn't implement an equatable method. I think it would make more sense to have it be an optional implementation. Where is the fault in my logic?

Upvotes: 1

Views: 336

Answers (2)

matt
matt

Reputation: 535547

The problem being solved is a limitation of generics.

Let's say we have a Bird struct and Insect struct. A generic equatable lets us define == where the actual object types are the same type. So we can make Bird adopt Equatable so that if we have b1 and b2 both typed as Bird we can decide whether they are equal. And we can make Insect adopt Equatable so that if we have i1 and i2 both typed as Insect we can decide whether they are equal.

But now suppose both Bird and Insect adopt the Flier protocol. You cannot make Flier adopt Equatable, because there's a limitation in how generics work. So if two objects are typed as Flier, you have no way of implementing equatability for them.

The video demonstrates that protocol extensions solve this problem. With a protocol extension on Flier, you can define a different method that compares two Fliers and decides whether they are equal - namely, by deciding first whether they are of the same class and then applying ==. Thus you can make sense of equatability for a Flier (a protocol).

Upvotes: 3

nhgrif
nhgrif

Reputation: 62062

I can only guess as to why isEqualTo: is a required method for the drawable protocol. Perhaps so that whatever is drawing these things never wastes time drawing the same thing twice?

I can comment on the rest of it however.

Equatable is a Swift protocol (not available in Objective-C) which requires that there is a == operator defined for the type.

In Objective-C, there is no operator overloading. Moreover, in Objective-C, using == to compare objects simply compares their pointers. If the objects are the same object (same memory location), == returns true. If we want to see if the objects are different objects but still considered equal, we must use isEqualTo:. This is a method defined by NSObject and the default implementation simply returns the result of == comparison. But classes tend to override this.

In Swift, == has different behavior. In Swift, == returns behaves similarly to how we expect the isEqualTo: method to behave in Objective-C. That's because Swift has the === operator for comparing references. === returns true if these objects are the same (same memory location), but == is a custom implemented method that determines whether the objects are considered equal even if they are in different memory locations.

So I'm guessing the Drawable protocol has Objective-C classes in mind when it declares isEqualTo: as one of its required methods.

We could alternatively write the Drawable protocol as such:

protocol Drawable: Equatable {
    func draw()
}

From a strictly Swift perspective, this is a roughly equivalent protocol definition. But this means that whoever is using Drawable objects expects to compare them with == in Swift rather than isEqualTo:. And moreover, this means if we want to use any Objective-C defined objects with the protocol, now we must implement a custom == function for each of them, which most likely looks like this:

func == (left ObjCClass, right ObjCClass) -> Bool {
    return left.isEqualTo(right)
}

But we have to do this for every Objective-C class we want to define as Drawable.

The alternative is to define our protocol as you presented it, using isEqualTo:, a very commonplace Objective-C method.

Now, to make all of our Swift types conform to Drawable, all we have to do is implement the draw() method and conform to Equatable. As long as we conform to Equatable, the extension will add the isEqualTo: method to our Swift type as a simple return left == right effectively (and the existence of the == method is what the Equatable protocol guarantees).

Upvotes: 1

Related Questions