14

Let's say I'm implementing a root class in Swift, which I declare adopts the Equatable protocol (I want to be able to tell if an array of my type contains a given instance or not).

What is the difference -if any, in this specific case- between implementing the protocol's required == operator as:

public static func ==(lhs: MyClass, rhs: MyClass) -> Bool {

    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

...as opposed to just doing this:

public static func ==(lhs: MyClass, rhs: MyClass) -> Bool {

    return (lhs === rhs)
}

As a reference, this is what the documentation says about ObjectIdentifier():

A unique identifier for a class instance or metatype. In Swift, only class instances and metatypes have unique identities. There is no notion of identity for structs, enums, functions, or tuples.

...and this is what the "Basic Operators" section of The Swift Programming Language (Swift 3) says about the === operator:

NOTE

Swift also provides two identity operators (=== and !==), which you use to test whether two object references both refer to the same object instance. For more information, see Classes and Structures.

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189

1 Answers1

17

There is no difference for class instance, see the following comments in ObjectIdentifier.swift:

  /// Creates an instance that uniquely identifies the given class instance.
  ///
  /// The following example creates an example class `A` and compares instances
  /// of the class using their object identifiers and the identical-to
  /// operator (`===`):
  ///
  ///     class IntegerRef {
  ///         let value: Int
  ///         init(_ value: Int) {
  ///             self.value = value
  ///         }
  ///     }
  ///
  ///     let x = IntegerRef(10)
  ///     let y = x
  ///
  ///     print(ObjectIdentifier(x) == ObjectIdentifier(y))
  ///     // Prints "true"
  ///     print(x === y)
  ///     // Prints "true"
  ///
  ///     let z = IntegerRef(10)
  ///     print(ObjectIdentifier(x) == ObjectIdentifier(z))
  ///     // Prints "false"
  ///     print(x === z)
  ///     // Prints "false"
  ///

It also becomes apparent from the implementation of == for ObjectIdentifier, which just compares the pointers to the object storage:

  public static func == (x: ObjectIdentifier, y: ObjectIdentifier) -> Bool {
    return Bool(Builtin.cmp_eq_RawPointer(x._value, y._value))
  }

which is what the === operator does as well:

public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
  switch (lhs, rhs) {
  case let (l?, r?):
    return Bool(Builtin.cmp_eq_RawPointer(
        Builtin.bridgeToRawPointer(Builtin.castToUnknownObject(l)),
        Builtin.bridgeToRawPointer(Builtin.castToUnknownObject(r))
      ))
  case (nil, nil):
    return true
  default:
    return false
  }
}

ObjectIdentifier conforms to Hashable, so it is useful if you want to implement that protocol for your class:

extension MyClass: Hashable {
    var hashValue: Int {
        return ObjectIdentifier(self).hashValue
    }
}

An object identifier can also be created for meta types (e.g. ObjectIdentifier(Float.self)) for which === is not defined.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thank you for the very detailed answer. I was expecting no big different for my own class types, but this is very enlightening. I should have thought of checking the source code myself, now that Swift is open source! – Nicolas Miari Sep 20 '16 at 07:08
  • 5
    `===` operator [now directly compares](https://github.com/apple/swift/blob/b330c6078fd233ea7206158a20b21b4672d75cc3/stdlib/public/core/Equatable.swift#L249) `ObjectIdentifier` of parameters. – dispatchMain Mar 08 '18 at 03:57