daspianist daspianist - 27 days ago 6
iOS Question

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!

Answer Source

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