func == from equatable protocol does not work for custom object, swift
Asked Answered
C

2

6

My goal is to show a user list of history logins ( such as username ) if there are any. In order to do that, I am doing

1. Create an custom object named User like below

 class User: NSObject
    {
        var login: String

        init(login: String)
        {
            self.login = login
        }
        required init(coder aDecoder: NSCoder) {
            login = aDecoder.decodeObjectForKey("login") as! String
        }

        func encodeWithCoder(aCoder: NSCoder) {
            aCoder.encodeObject(login, forKey: "login")
        }
    }

    // This conform to make sure that I compare the `login` of 2 Users
    func ==(lhs: User, rhs: User) -> Bool
    {
        return lhs.login == rhs.login
    }

At UserManager, Im doing save and retrieve an User. Before saving, I'm doing a check if the the list of history logins contains a User, I wont add it in, otherwise.

class UserManager : NSObject
{
    static let sharedInstance   =   UserManager()
    var userDefaults            =   NSUserDefaults.standardUserDefaults()

    func saveUser(user:User)
    {
        var users = retrieveAllUsers()

        // Check before adding
        if !(users.contains(user))
        {
            users.append(user)
        }


        let encodedData =   NSKeyedArchiver.archivedDataWithRootObject(users)
        userDefaults.setObject(encodedData, forKey: "users")
        userDefaults.synchronize()
    }

    func retrieveAllUsers() -> [User]
    {
        guard let data  =   userDefaults.objectForKey("users") as? NSData else
        {
            return [User]()
        }
        let users   =   NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [User]
        // Testing purpose
        for user in users
        {
            print(user.login)
        }
        return users
    }
}

At first time trying, I do

UserManager.sharedInstance.saveUser(User(login: "1234"))

Now it saves the first login. At second time, I also do

UserManager.sharedInstance.saveUser(User(login: "1234"))

UserManager still adds the second login into nsuserdefault. That means the function contains fails and it leads to

func ==(lhs: User, rhs: User) -> Bool
{
    return lhs.login == rhs.login
}

does not work properly.

Does anyone know why or have any ideas about this.

Condensation answered 14/7, 2016 at 20:59 Comment(1)
Related: #33320459Acedia
P
14

The problem is that User derives from NSObject. This means that (as you rightly say) your == implementation is never being consulted. Swift's behavior is different for objects that derive from NSObject; it does things the Objective-C way. To implement equatability on an object that derives from NSObject, override isEqual:. That is what makes an NSObject-derived object equatable in a custom way, in both Objective-C and Swift.

Just paste this code right into your User class declaration, and contains will start working as you wish:

override func isEqual(object: AnyObject?) -> Bool {
    if let other = object as? User {
        if other.login == self.login {
            return true
        }
    }
    return false
}
Portraitist answered 14/7, 2016 at 21:4 Comment(2)
thanks. it works. However, i want to fully understand. I know other is a new Userand self will be previous User which are form the list. My uncertainty is how self can be referenced to previous User from the list.Condensation
Don't worry about that. After all, you didn't worry about it for ==. All you need to know is that contains depends on this.Portraitist
C
3

What's going on?

As @matt already said, the problem is about equality.

Look

var users = [User]()
users.append(User(login: "1234"))
users.contains(User(login: "1234")) // false

Look again

var users = [User]()
let user = User(login: "1234")
users.append(user)
users.contains(user) // true <---- THIS HAS CHANGED

contains

The contains function is NOT using the logic you defined here

func ==(lhs: User, rhs: User) -> Bool {
    return lhs.login == rhs.login
}

Infact it is simply comparing the memory addresses of the objects.

Solution

You can solve the issue passing your own logic to contains, just replace this

if !(users.contains(user)) {
    users.append(user)
}

with this

if !(users.contains { $0.login == user.login }) {
    users.append(user)
}
Cool answered 14/7, 2016 at 21:11 Comment(6)
I also tried and it works as well. I was surprised that contains logic is used to compare the memory address. Can you explain this line $0.login == user.login .What is $0 means Further more, it seems to be that I dont need to conform func == in my code any more.Condensation
@tonytran: The problem is that you inherit NSObject so the isEqual method is invoked to check for equality.Cool
@tonytran: { $0.login == user.login } here $0 is the n-th element of users. This closure is applied to every element of the array until it is true. In this case the contains returns true. Otherwise contains does false.Cool
@tonytran: YES users.contains { $0 == user } will work fine.Cool
:please dont shout :). and how is about === ?Condensation
@tonytran: No problem :D This === always compare memory addressesCool

© 2022 - 2024 — McMap. All rights reserved.