Search code examples
iosswiftnsobject

How to override NSObject's default comparison in Swift


I have a custom class Player that conforms to NSObject, NSCoding. A Player object is instantiated with a id: String!

One of the issues I am encountering is when I perform an operation like the following:

let listOfPlayers = [Player]()
//populate listOfPlayers
if listOfPlayers.contains(aPlayer) ...

Specifically, the contains would return results based on the memory pointer, and not based on the id value. As such, I would get a true in some cases and false in others.

I would like to override the default comparison methods, and did the following:

func isEqual(object: AnyObject?) -> Bool {
    if let otherPlayer = object as? Player {
        return self.id == otherPlayer.id
    }
    return false
}


static func == (lhs: Player, rhs: Player) -> Bool {
    return lhs.id == rhs.id
}

However, these functions are not being executed. I have also tried to add override, but it returns an error "Method does not override any method from its superclass`

What is the right way to have a customer comparator so it is only comparing the id element?

Thanks!


Solution

  • As of Swift 3, the isEqual method of NSObject takes an Any? parameter, so you are not overriding the correct method, that's why it is never called.

    You should also override var hash: Int (equal objects must have the same hash) – otherwise the object will behave wrongly in hashable collections (sets, dictionaries):

    class Player: NSObject {
        let id: String
    
        init(id: String) { self.id = id }
    
        override func isEqual(_ object: Any?) -> Bool {
            if let other = object as? Player {
                return self.id == other.id
            } else {
                return false
            }
        }
    
        override var hash: Int {
            return id.hashValue
        }
    }
    

    Some tests:

    let p1 = Player(id: "a")
    let p2 = Player(id: "a")
    
    print(p1 == p2) // true
    print(p1 != p2) // false
    
    // Native Swift set:
    let set = Set([Player(id: "x"), Player(id: "y"), Player(id: "z")])
    print(set.contains(Player(id: "y"))) // true
    
    // Foundation set:
    let nsset = NSSet(objects: Player(id: "x"), Player(id: "y"), Player(id: "z"))
    print(nsset.contains(Player(id: "y"))) // true