Swift Array.contains() doesn't call Equatable function of PFUser subclass [duplicate]
Asked Answered
T

1

10

I have a subclass of PFUser - MYUser class with implementation of Equatable function for comparing objectIds this way:

func ==(left: MYUser, right: MYUser) -> Bool {
    return left.objectId == right.objectId
}

But when I call Array.contains() method it doesn't call this implementation of Equatable function, that leads to incorrect results. For instance, here:

let hasUser = self.selectedUsers.contains(currentUser)

hasUser becomes false if selectedUsers array contains different memory object but with the same objectId as in currentUser.

What interesting, Equatable function implementation is called in direct usage. Here:

var hasUser = false
for itUser in self.selectedUsers {
  if itUser == currentUser {
    hasUser = true
    break
  }
}

== operator successfully was called and hasUser has correct values for different memory objects but with the same objectId

What can be the cause of it?

UPDATE. Here is MYUser class:

class MYUser: PFUser {

    // MARK: - Parse Object

    @NSManaged var avatarFile: PFFile?
    @NSManaged var fullName: String?

    // MARK: - PFSubclassing Methods (through PFUser)

    override class func initialize() {
        struct Static {
            static var onceToken : dispatch_once_t = 0;
        }
        dispatch_once(&Static.onceToken) {
            self.registerSubclass()
        }
    }
}

func ==(left: MYUser, right: MYUser) -> Bool {
    return left.objectId == right.objectId
}
Tshombe answered 3/1, 2016 at 18:33 Comment(4)
I have no experience with Parse.com, but this may be related: #33320459 – try to override isEqual: instead of ==.Wofford
@MartinR I don't think it's the only matter of Parse and ObjC interoperability. I created pure Swift MWE and also overloaded == for subclass isn't called in contains.Mosqueda
Maybe it's no relevant but can you show us how MYUser is defined?Photocurrent
@appzYourLife I added class implementation in the questionTshombe
P
14

I think is this a NSObject issue.

class MYUserNSObject: NSObject {
    dynamic var fullName: String

    init(fullName: String) {
        self.fullName = fullName
        super.init()
    }
}

func ==(left: MYUserNSObject, right: MYUserNSObject) -> Bool {
    return left.fullName == right.fullName
}

let objectUsers = [MYUserNSObject(fullName: "a"), MYUserNSObject(fullName: "b")]
let objectResult = objectUsers.contains(MYUserNSObject(fullName: "a"))
print("\(result)")

Prints false.

class MYUserSwift: Equatable {
    var fullName: String

    init(fullName: String) {
        self.fullName = fullName
    }
}

func ==(left: MYUserSwift, right: MYUserSwift) -> Bool {
    return left.fullName == right.fullName
}

let swiftUsers = [MYUserSwift(fullName: "a"), MYUserSwift(fullName: "b")]
let swiftResult = swiftUsers.contains(MYUserSwift(fullName: "a"))
print("\(swiftResult)")

Prints true.


Finally, by adding -isEqual:, I fixed this.

class MYUserNSObject: NSObject {
    dynamic var fullName: String

    init(fullName: String) {
        self.fullName = fullName
        super.init()
    }

    override func isEqual(object: AnyObject?) -> Bool {
        guard let user = object as? MYUserNSObject else { return false }
        return self == user
    }
}

func ==(left: MYUserNSObject, right: MYUserNSObject) -> Bool {
    return left.fullName == right.fullName
}

let objectUsers = [MYUserNSObject(fullName: "a"), MYUserNSObject(fullName: "b")]
let objectResult = objectUsers.contains(MYUserNSObject(fullName: "a"))
print("\(objectResult)")

Prints true.


Updated for Swift 4.0

class MYUserNSObject: NSObject {
    @objc var fullName: String

    init(fullName: String) {
        self.fullName = fullName
        super.init()
    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let user = object as? MYUserNSObject else { return false }
        return self.fullName == user.fullName
    }
}

let objectUsers = [MYUserNSObject(fullName: "a"), MYUserNSObject(fullName: "b")]
let objectResult = objectUsers.contains(MYUserNSObject(fullName: "a"))
print("\(objectResult)")

Prints true.

Note: there is no longer a ==(left:right:) function needed.

let success = MYUserNSObject(fullName: "a") == objectUsers[0]
print("success should be true: \(success)")

let failure = MYUserNSObject(fullName: "a") == objectUsers[1]
print("failure should be false: \(failure)")
Puritan answered 4/1, 2016 at 13:6 Comment(6)
Yes, it seems that Array.contains() specially calls to -isEquals: method for NSObject subclasses. Thank you.Tshombe
Hey Jeffrey, thank you so much for providing a solution. This has saved me so much time. This is the best answer to compare two custom classes using == operator overloading. Thanks again :)Oxytetracycline
Update for latest Swift, the prototype for isEqual is "Any?" not "AnyObject?"Canoness
@Canoness updated.Puritan
it works like a charm !Aggressive
thank you, still works on swift 5.2. apparently Array.contains() calls to isEquals instead of default equatable methodsTorry

© 2022 - 2024 — McMap. All rights reserved.