Reputation: 502
I'm trying to compare the properties of two instances of a class. I used this before and it's working fine:
override public func isEqual(_ object: Any?) -> Bool {
if let rhs = object as? BankDataModel, self.accountHolderName == rhs.accountHolderName, self.accountNumber == rhs.accountNumber,
self.accountHolderThirdPartyAccountOwner == rhs.accountHolderThirdPartyAccountOwner, self.bankName == rhs.bankName, self.bankRoutingNumber == rhs.bankRoutingNumber, self.bic == rhs.bic, self.iban == rhs.iban, self.directDebitStatus == rhs.directDebitStatus, self.sepaCreditorID == rhs.sepaCreditorID, self.sepaMandateID == rhs.sepaMandateID{
return true
}else{
return false
}
}
However I have to write this for all of my model classes again if I want to use this. I want to have a function which I can use for all my model classes, so I have used the Mirror struct and tried something like this:
override public func isEqual(_ object: Any?) -> Bool {
if let rhs = object as? BankDataModel{
return compareTwoObjects(objectToCompare: rhs, objectSelf: self)
}else{
return false
}
}
The overridden isEqual method in each model will call this method from a Helper class, so I can use this in every Model that I have:
func compareTwoObjects(objectToCompare: Any, objectSelf: Any) -> Bool{
let objectSelf2 = Mirror(reflecting: objectSelf)
let objectToCompare2 = Mirror(reflecting: objectToCompare)
var index = objectToCompare2.children.startIndex
for attr in objectSelf2.children{
let type1 = Mirror(reflecting: attr.value).subjectType
print(type1)
let type2 = Mirror(reflecting: objectToCompare2.children[index].value).subjectType
print(type2)
if type1 == type2{
print("Equal")
if attr.value as! type1 != objectToCompare2.children[index].value as! type2{
return false
}
}
index = objectToCompare2.children.index(index, offsetBy: 1)
}
return true
}
The most of the code is working fine, the print("Equal")
is always executed when the types of the two variables are equal.
The problem is this line:
if attr.value as! type1 != objectToCompare.children[index].value as! type2{
return false
}
I get this error message: "Use of undeclared type "type1"". If I don't use the casting to type1 and type2, I get the error that I cannot use == with two properties of type "Any". Is there a way to convert the type1 and type2 Strings to real datatypes? Or is there maybe a better way for what I'm trying to achieve?
This is my output from the prints:
Optional<String>
Optional<String>
Equal
Optional<String>
Optional<String>
Equal
Optional<Bool>
Optional<Bool>
Equal
String
String
Equal
Optional<String>
Optional<String>
Equal
String
String
Equal
String
String
Equal
Optional<DirectDebitStatus>
Optional<DirectDebitStatus>
Equal
I would appreciate any help!
Upvotes: 2
Views: 793
Reputation: 21
I had a similar problem. Needed to compare several (huge) model objects that I got from different sources. Starting with your code and expanding with recursion and special cases I ended up with the following code snippet.
(Please note that I use this in a Test class, so there's no need to be efficient.)
func compareProperties(of target1: Any, with target2: Any) -> Bool {
let mirror1 = Mirror(reflecting: target1)
let mirror2 = Mirror(reflecting: target2)
var index = mirror1.children.startIndex
for child in mirror1.children {
let child2 = mirror2.children[index]
//Make another mirror to see if it has children
let innerMirror1 = Mirror(reflecting: child.value)
if (innerMirror1.children.count > 0) {
// Use recursive reflection on the value of each child.
print("Recursion on \(innerMirror1.subjectType)")
if (!compareProperties(of: child.value, with: child2.value)) {
return false
}
} else {
//Compare the values
let type1 = innerMirror1.subjectType
let innerMirror2 = Mirror(reflecting: child2.value)
let type2 = innerMirror2.subjectType
if type1 == type2 {
let stringValue1 = "\(child.value)"
let stringValue2 = "\(child2.value)"
//print("\(type1): \(stringValue1) ==? \(type2): \(stringValue2)")
if (stringValue1 != stringValue2) {
print("\(type1): \(stringValue1) != \(type2): \(stringValue2)")
//Special case with Dates
if ("\(mirror1.subjectType)" == "Date") {
//Skip comparison of Doubles in Dates
if ("\(target1 as? Date)" != "\(target2 as? Date)") {
return false
}
} else {
return false
}
}
} else {
print("Different classes \(type1) and \(type2)")
return false
}
}
//Increase index for next value pair
index = mirror1.children.index(index, offsetBy: 1)
}
//If we get here it was never false.
return true
}
Upvotes: 2
Reputation: 3971
This might be what you're looking for, it compares two Any?
objects by casting them to NSObject
first.
private func equalAny<BaseType: Equatable>(lhs: Any?, rhs: Any?, baseType: BaseType.Type) -> Bool {
if (lhs == nil && rhs != nil) || (rhs == nil && lhs != nil) { return false }
if lhs == nil && rhs == nil { return true }
guard let lhsEquatable = lhs as? BaseType, let rhsEquatable = rhs as? BaseType else {
return false
}
return lhsEquatable == rhsEquatable
}
Make sure to use NSObject
as the base type. Call the method like this:
equalAny(lhs: value, rhs: otherValue, baseType: NSObject.self)
Upvotes: 1
Reputation: 907
Swift 4
I think you might want to use the Equatable
interface. With it you can achieve something like this:
extension BankDataModel: Equatable {
static func == (lhs: BankDataModel, rhs: BankDataModel) -> Bool {
return
lhs.accountHolderName == rhs.accountHolderName &&
lhs.accountNumber == rhs.accountNumber &&
lhs.accountHolderThirdPartyAccountOwner == rhs.accountHolderThirdPartyAccountOwner
}
}
Note: you can extend the return statement with additional &&
clauses to increase the 'equal'-checking.
Then wherever you put this extension you can compare two BankDataModel
with each other using the ==
operator, like so:
let bankAModel: BankDataModel = // Some model
let bankBModel: BankDataModel = // Also some model
if bankAModel == bankBModel {
// Do stuff
}
Expansion
The best case I can think of is that you implement both Equatable
and Hashable
on BankDataModel
so that it results into a clean comparison method on the Equatable
side (only comparing hashes there).
This way results in better maintainable and cleaner code. See also separation of concerns pattern.
Upvotes: 4