Swift: Overriding == in subclass results invocation of == in superclass only
The reason the equality for A
is being invoked for an Array<A>
that contains B
is that overloading of free functions is resolved statically, not dynamically – that is, at compile time based on the type, not at runtime based on the pointed-to value.
This is not surprising given ==
is not declared inside the class and then overridden in the subclass. This might seem very limiting but honestly, defining polymorphic equality using traditional OO techniques is extremely (and deceptively) difficult. See this link and this paper for more info.
The naïve solution might be to define a dynamically dispatched function in A
, then define ==
to just call that:
class A: Equatable {
func equalTo(rhs: A) -> Bool {
// whatever equality means for two As
}
}
func ==(lhs: A, rhs: A) -> Bool {
return lhs.equalTo(rhs)
}
Then when you implement B
, you’d override equalTo
:
class B: A {
override func equalTo(rhs: A) -> Bool {
return (rhs as? B).map { b in
return // whatever it means for two Bs to be equal
} ?? false // false, assuming a B and an A can’t be Equal
}
}
You still have to do one as?
dance, because you need to determine if the right-hand argument is a B
(if equalTo
took a B
directly, it wouldn’t be a legitimate override).
There’s also still some possibly surprising behaviour hidden in here:
let x: [A] = [B()]
let y: [A] = [A()]
// this runs B’s equalTo
x == y
// this runs A’s equalTo
y == x
That is, the order of the arguments changes the behaviour. This is not good – people expect equality to be symmetric. So really you’d need some of the techniques described in the links above to solve this properly.
At which point you might feel like all this is getting a bit unnecessary. And it probably is, especially given the following comment in the documentation for Equatable
in the Swift standard library:
Equality implies substitutability. When
x == y
,x
andy
are interchangeable in any code that only depends on their values.Class instance identity as distinguished by triple-equals
===
is
notably not part of an instance's value. Exposing other non-value
aspects ofEquatable
types is discouraged, and any that are
exposed should be explicitly pointed out in documentation.
Given this, you might seriously want to reconsider getting fancy with your Equatable
implementation, if the way you’re implementing equality is not in a way where you’d be happy with two values being equal being substituted with each other. One way to avoid this is to consider object identity to be the measure of equality, and implement ==
in terms of ===
, which only needs to be done once for the superclass. Alternatively, you could ask yourself, do you really need implementation inheritance? And if not, consider ditching it and using value types instead, and then using protocols and generics to capture the polymorphic behaviour you’re looking for.
Overriding equals with generic class
As Airspeed suggests the problem is that operator's implementation is not a part of class/struct implementation => hence, inheritance does not work there.
What you can do is to keep the logic inside of the class implementation and make operators use it. E.g. the following will do what you need:
class MySuperClass: Equatable {
func isEqualTo(anotherSuperClass: MySuperClass) -> Bool {
fatalError("Must override")
}
}
func == (lhs: MySuperClass, rhs: MySuperClass) -> Bool {
return lhs.isEqualTo(rhs)
}
class MySubClass<T>:MySuperClass {
let id: Int
init(_ id: Int) {
self.id = id
}
override func isEqualTo(anotherSuperClass: MySuperClass) -> Bool {
if let anotherSubClass = anotherSuperClass as? MySubClass<T> {
return self.id == anotherSubClass.id
}
return super.isEqualTo(anotherSuperClass) // Updated after AirSpeed remark
}
}
let a = MySubClass<Any>(1)
let b = MySubClass<Any>(2)
let c = MySubClass<Any>(2)
a == b
b == c
... as you can see ==
operator is defined only once and it uses MySuperClass
's method to figure out whether its two arguments are equal. And after that .isEqualTo()
handles the rest, including the use of inheritance mechanism on MySubClass
level.
UPD
The benefit of above approach is that the following will still work:
let a2: MySuperClass = a
let b2: MySuperClass = b
let c2: MySuperClass = c
a2 == b2
b2 == c2
... i.e. regardless of the variable type at compile-time the behaviour will be determined by the actual instance type.
Swift override protocol methods in sub classes
Use protocol extension with where clause. It works.
But I would not recommend you to have such things in your codebase.
class BaseViewController: UIViewController {
}
extension OptionsDelegate where Self: BaseViewController {
func handleSortAndFilter(opt: Options) {
print("Base class implementation")
}
}
extension BaseViewController: OptionsDelegate {
}
class InsipartionsViewController: BaseViewController {
}
extension OptionsDelegate where Self: InsipartionsViewController {
func handleSortAndFilter(opt: Options) {
print("Inspirations class implementation")
}
}
Swift 2.0: Parametrized classes don't call proper == function if it inherits from class that is Equatable
Though I didn't find anything on the Swift reference about this, this gives us a clue:
Generics are lower down the pecking order. Remember, Swift likes to be as “specific” as possible, and generics are less specific. Functions with non-generic arguments (even ones that are protocols) are always preferred over generic ones:
This doens't seem to have any relation to Equatable
, though; this test shows us the same behaviour:
class Foo {};
class Bar<T>: Foo {};
class Baz: Bar<Int> {};
class Qux<T>: Baz {};
func test(foo: Foo) {
print("Foo version!");
};
func test<T>(bar: Bar<T>) {
print("Bar version!");
};
func test(baz: Baz) {
print("Baz version!");
};
func test<T>(qux: Qux<T>) {
print("Qux version!");
};
let foo = Foo();
let bar = Bar<Int>();
let baz = Baz();
let baz2: Bar<Int> = Baz();
let qux = Qux<Float>();
test(foo); // Foo
test(bar); // Foo
test(baz); // Baz
test(baz2); // Foo
test(qux); // Baz
So what is happening here is that when choosing a free function, along with using its static type instead of the dynamic type, Swift prefers not to use any generics, even if that generic is a type parameter and indeed it should be the most specialized choice.
So, it seems that to solve the issue, as suggested by @VMAtm, you should add a method like equalTo
to the class instead, so that the actual method is picked up at runtime.
Swift calling subclass's overridden method from superclass
Make sure that the object's class is SubclassViewController
. Otherwise, it will not have any knowledge of the method which is overriden by subclass
How to call the superclass implementation of an overridden method from inside the superclass in Java?
You could move said method body to a private method and let the default method (the one which may be overridden by the subclass) delegate to the former. See this example
public abstract class SuperClass {
private Object field1;
protected SuperClass(Object obj){
// call the safe implementation
setField1Safe(obj);
}
public void setField1(Object obj){
// just delegates
setField1Safe(obj);
}
private void setField1Safe(Object obj){
// perform some check on obj and
// then set field1 such that field1==obj
}
}
public class SubClass extends SuperClass{
public SubClass(Object obj){
super(obj);
}
@Override
public void setField1(Object obj){
super.setField1(obj);
// do some work that is necessary only when
// field1 is set through SubClass.setField1()
}
}
That way the sub class can still override setField1
but if you really depend on the implementation then you can call the private setField1Safe
method.
Related Topics
How to Create Swift Class for Category
How to Make Class Methods/Properties in Swift
Variable 'Xxx' Was Never Mutated, Consider Changing to 'Let'
Stop a Dispatchqueue That Is Running on the Main Thread
Handling Multiple Gesturerecognizers
Swift 2/iOS 9 - Libz.Dylib Not Found
Any Way to Iterate a Tuple in Swift
Swift Protocol Error: 'Weak' Cannot Be Applied to Non-Class Type
Show Am/Pm in Capitals in Swift
How to Create a View-Based Nstableview Purely in Code
Swift Semantics Regarding Dictionary Access
Cannot Resolve Swift Packages After 15Th March 2022 in Xcode
String Convert to Int and Replace Comma to Plus Sign