Friday, March 17, 2023

Equality in Swift: NSObject, Subclasses, and Existentials

Mark Newton:

Conformance to the Equatable protocol seems pretty straightforward. You simply override the == function.

[…]

This works great for objects like structs, or classes with no superclass. However, we can run into problems with the == function if we’re dealing with NSObject subclasses.

Jayesh Kawli (via Marcin Krzyzanowski):

And now if you try to do following comparison, they will either won’t work, will be buggy or fail to compile

Noah Gilmore:

Swift can be tricky sometimes. For example, what does the following print?

class A: NSObject {
  let x: Int

  init(x: Int) {
    self.x = x
  }
}

func ==(left: A, right: A) -> Bool {
  return left.x == right.x
}

print(A(x: 1) == A(x: 1))
print([A(x: 1)] == [A(x: 1)])

[…]

The best way to make an NSObject subclass use custom equality inside an array is to override isEqual:[…]

His reasoning for why the custom == didn’t work as expected is wrong, but the solution is correct. Similarly, you should override hashValue (not hash(into:)) if you need to change how it is Hashable.

For non-NSObject classes, a similar issue applies. If you have something like:

class Base: Equatable {
    static func == (lhs: Base, rhs: Base) -> Bool {
        return lhs === rhs
    }
}

class A: Base {
    let x: Int

    init(x: Int) {
      self.x = x
    }
}

func ==(left: A, right: A) -> Bool {
    return left.x == right.x
}

The results may not be what you expect:

A(x: 1) == A(x: 1) // true
[A(x: 1)] == [A(x: 1)] // false

Array uses the == from where its elements conformed to Equatable.

Natalia Panferova (tweet):

In Swift 5.7 that comes with Xcode 14 we can more easily check if two values of type Any are equal, because we can cast values to any Equatable and also use any Equatable as a parameter type thanks to Unlock existentials for all protocols change.

[…]

Inside isEqual() method we have access to Self, so we can try to cast the other value to the same type as the concrete type of the value this method is called on. If the cast fails, the two values cannot be equal, so we return false. If it succeeds, then we can check the two values for equality, using == function on values of the same concrete type.

There are some edge cases to be aware of, however, so it is preferred to use AnyHashable.

Ben Cohen:

You can define something similar for Comparable[…]

Previously:

Update (2023-08-09): See also: Helge Heß.

Comments RSS · Twitter · Mastodon

Leave a Comment