17

Given the following class, how can all the values in two instances be compared to each other?

// Client Object
//
class PLClient {
    var name = String()
    var id = String()
    var email = String()
    var mobile = String()
    var companyId = String()
    var companyName = String()

    convenience init (copyFrom: PLClient) {
        self.init()
        self.name =  copyFrom.name
        self.email = copyFrom.email
        self.mobile = copyFrom.mobile
        self.companyId = copyFrom.companyId
        self.companyName = copyFrom.companyName

    }

}

var clientOne = PLClient()

var clientTwo = PLClient(copyFrom: clientOne)

if clientOne == clientTwo {   // Binary operator "==" cannot be applied to two PLClient operands
    println("No changes made")
} else {
    println("Changes made. Updating server.")
}

The use-case for this is in an application which presents data from a server. Once the data is converted into an object, a copy of the object is made. The user is able to edit various fields etc.. which changes the values in one of the objects.

The main object, which may have been updated, needs to be compared to the copy of that object. If the objects are equal (the values of all properties are the same) then nothing happens. If any of the values are not equal then the application submits the changes to the server.

As is shown in the code sample, the == operator is not accepted because a value is not specified. Using === will not achieve the desired result because these will always be two separate instances.

Michael Voccola
  • 1,827
  • 6
  • 20
  • 46

4 Answers4

53

Indicate that your class conforms to the Equatable protocol, and then implement the == operator.

Something like this:

class PLClient: Equatable 
{
    var name = String()
    var id = String()
    var email = String()
    var mobile = String()
    var companyId = String()
    var companyName = String()
    //The rest of your class code goes here

    public static func ==(lhs: PLClient, rhs: PLClient) -> Bool{
        return 
            lhs.name == rhs.name &&
            lhs.id == rhs.id &&
            lhs.email == rhs.email &&
            lhs.mobile == rhs.mobile &&
            lhs.companyId == rhs.companyId &&
            lhs.companyName == rhs.companyName
    }
}
dustinrwh
  • 888
  • 1
  • 14
  • 16
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • What is the best way to compare if one or two of these properties are optional? Eg if `name: String?` – Parth May 17 '21 at 22:26
  • Note that for a data-only object like the example, it should probably be a struct, not a class (since those are value types.) And in newer versions of Swift, if all a struct's properties conform to Equatable, so does the entire struct. The example above would "just work" if you made it a struct not a class. – Duncan C Mar 25 '22 at 15:32
9

Working off of Duncan C's answer, I have come up with an alternative which is slightly clearer that it is being used in a custom way:

// Client Object
//
class PLClient {
    var name = String()
    var id = String()
    var email = String()
    var mobile = String()
    var companyId = String()
    var companyName = String()

    convenience init (copyFrom: PLClient) {
        self.init()
        self.name = copyFrom.name
        self.email = copyFrom.email
        self.mobile = copyFrom.mobile
        self.companyId = copyFrom.companyId
        self.companyName = copyFrom.companyName   
    }

    func equals (compareTo:PLClient) -> Bool {
        return
            self.name == compareTo.name &&
            self.email == compareTo.email &&
            self.mobile == compareTo.mobile
    }

}

var clientOne = PLClient()
var clientTwo = PLClient(copyFrom: clientOne)

if clientOne.equals(clientTwo) {
    println("No changes made")
} else {
    println("Changes made. Updating server.")
}
Michael Voccola
  • 1,827
  • 6
  • 20
  • 46
  • is there a case to write a comparitor method with the equivalent signature of "isEqualTo:" ? – Damo May 07 '15 at 16:29
  • 2
    In Swift, the correct way to implement an equality test is a class or struct is to have it conform to the Equatable protocol. I can't see any reason to implement a function such as "isEqualTo:" particularly since the class could later be extended to conform to the Equality protocol and then have two potentially different equality tests. In addition, an "isEqualTo:" method could be overridden by a subclass. That would be a Bad Thing because it breaks the "Subclass of A is an A" rule. The Equality protocol == function cannot be overridden by a subclass. – Vince O'Sullivan Jun 16 '17 at 07:49
  • 3
    Overall I have to disagree with this advice. The equatable protocol is intended for allowing objects to be compared for equality. By making a class conform to the equatable protocol, you are using the standard method for comparison. Using a custom "isEqualTo" method is non-standard, and makes things **less** clear, not more. – Duncan C Oct 13 '17 at 12:16
2

You could loop through fields by using keypath

I haven't tested this, but the general idea is there. Give a list of valid fields and loop through them instead of writing every single equatable. So it's the same as @duncan-c suggested but with looping.

Something like:

class PLClient:Equatable {
    var name = String()
    var id = String()
    var email = String()
    var mobile = String()
    var companyId = String()
    var companyName = String()

    public static func ==(lhs: PLClient, rhs: PLClient) -> Bool{
        let keys:[KeyPath<PLClient, String>] = [\.name, \.id, \.email, \.mobile, \.companyId, \.companyName]
        return keys.allSatisfy { lhs[keyPath: $0] == rhs[keyPath: $0] }
    }
}
Johnston
  • 20,196
  • 18
  • 72
  • 121
1

Try "is" keyword in swift, e.g.

if self.navigationController.topViewController is TestViewController {
//Logic here
}
Haseeb Javed
  • 1,769
  • 17
  • 20