Can we match identity using `switch`?

I was wondering if it's possible to somehow switch over identities in Swift. I have a couple of class instances (Buttons) and an instance which is a supertype of those instances.

Currenly I'm doing a bunch of if instance_1 === currentSuperTypeInstance (called differently in the real codebase), but I think it's very ugly and verbose.

Is there a way to use switch to find out if the identity of an instance is the same (similar to what === operator does)?

2 Likes

One possibility would be to define your own ~= operator for this purpose, perhaps for matching ObjectIdentifier to AnyObject:

func ~=(id: ObjectIdentifier, object: AnyObject) -> Bool {
  return id == ObjectIdentifier(object)
}

class X {}
let x = X()
let y = X()

switch x {
  case ObjectIdentifier(x): print("x")
  case ObjectIdentifier(y): print("y")
  default: print("something else")
}
4 Likes

Thx Joe. I never was comfortable to use the ~= operator. The pattern from switch will be passed to the first parameter of the ~= overload while the expression before the switch body will land as the second parameter right?

That's correct.

1 Like

At the risk of stating the obvious: If that happen to be instances of UIButton (or NSButton) then you can just match against the instances, for example

@IBOutlet var button1, button2: UIButton!

@IBAction func action(_ sender: UIControl) {
    switch sender {
    case button1: print("button 1")
    case button2: print("button 2")
    default: break
    }
}

in a UIViewController.

1 Like

It is indeed something similar, but without any @IB stuff. Why does this work? Is there an overloaded ~= operator in UIKit that I don't know about?

1 Like

NSObject subclasses are all Equatable using NSObject's -isEqual: implementation. That'll work for identity testing as long as the subclass doesn't override -isEqual: with different meaning. If you want to ensure you're doing only identity testing, it'd be better to use a matching operation that explicitly uses identity.

4 Likes

See "Object Comparison" in Interacting with Objective-C APIs:

Swift provides default implementations of the == and === operators and adopts the Equatable protocol for objects that derive from the NSObject class. The default implementation of the == operator invokes the isEqual: method, ...

The base implementation of the isEqual: provided by the NSObject class is equivalent to an identity check by pointer equality. ...

1 Like

Thanks, I missed that part obviously.

NSString, NSNumber are are typical examples where isEqual: is overridden. I would assume that this is not the case for UIControl and its subclasses, but am not sure if one can always rely on it.

Yeah as Joe just said we're on the safe side if we provide a pattern that explicitly uses a custom overloaded ~= for that.

Is there a reason not to define

func ~=(pattern: AnyObject, object: AnyObject) -> Bool {
    return pattern === object
}

so that one can use it more simply as

class X {}
let x = X()
let y = X()

switch x {
case x: print("x")
case y: print("y")
default: print("something else")
}

?

That would also be unreliable for any class types that are Equatable or otherwise have access to more specific ~= overloads.

That makes sense, thanks.

So another option would be to match

switch ObjectIdentifier(x) {
case ObjectIdentifier(x): print("x")
case ObjectIdentifier(y): print("y")
default: print("something else")
}

without defining a custom ~= operator.

2 Likes

If it's standard view classes you are talking about don't worry, == and switching will check for identity.

I use the above method, too.

Just a tip: When the type name is repeated multiple times and adds to noise, I just add a local type alias right above the usage site to reduce visual clutter. Like this:

typealias Id = ObjectIdentifier

switch Id(x) {
case Id(x): print("x")
case Id(y): print("y")
default: print("something else")
}
1 Like

That's exactly why I don't trust == on subclasses of NSObject, it's an equal operator which should not check the identity but the equality of an instance.

== checks for substitutability, so if 2 instances with same values (e.g. 2 views with everything the same) are not substitutable (code won't perform the same outcomes if you swap them) then it makes sense that == actually works as ===.
More in general the object shouldn't conform to Equatable at all, but that can't be really done with NSObject while keeping it working together with isEqual:

I disagree with that (at least as long as we don't support factory inits for classes). An object in Swift currently is an instance of a class only which already means that this thing does require reference semantics and not value semantics. You can have multiple objects (non-subclasses of NSObject) that could have the same identifier and therefore considered equal regardless of all possible other states of such two objects.