like
class A :NSObject {
let a :Int
let b :UIColor
}
I don't want to implement isEqual
by comparing all properties one by one. If that, When I add another property, I should modify isEqual
's implement again.
When using Mirror
in swift, I could print all properties conveniently. How do I implement isEqual
method conveniently by using Mirror
.
You shouldn't use runtime introspection other than for diagnostics, and certainly not to avoid small amounts of "boilerplate" code or to avoid updating existing code.
Below follows, however, some comments on the subject, but note that these should be considered hacks and should not be used in any kind of production code. They can, however, show some example usage of runtime introspection in Swift.
class
/struct
"wrapper", using runtime introspection for property-by-property equality testingYou could implement an Equatable
-container for holding values of equatable types, which can (different from Equatable
itself) be casted to, which we will make use of to compare properties of say, a class
or a struct
.
/* Heterogeneous protocol acts as castable Equatable container used for
property-by-property equality testing in EquatableConstruct */
protocol PseudoEquatableType {
func isEqual(to other: PseudoEquatableType) -> Bool
}
extension PseudoEquatableType where Self : Equatable {
func isEqual(to other: PseudoEquatableType) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}
with the class
/struct
equatable "wrapper" and its conformance to Equatable
implemented (ab)using runtime introspection:
/* EquatableConstruct and its conformance to Equatable */
protocol EquatableConstruct : Equatable { }
func ==<T: EquatableConstruct>(lhs: T, rhs: T) -> Bool {
let mirrorLhs = Mirror(reflecting: lhs)
let mirrorRhs = Mirror(reflecting: rhs)
guard let displayStyle = mirrorLhs.displayStyle,
(displayStyle == .struct || displayStyle == .class) else {
print("Invalid use: type is not a construct.")
return false
}
let childrenLhs = mirrorLhs.children.filter { $0.label != nil }
let childrenRhs = mirrorRhs.children.filter { $0.label != nil }
guard childrenLhs.count == childrenRhs.count else { return false }
guard !childrenLhs.contains(where: { !($0.value is PseudoEquatableType) }) else {
print("Invalid use: not all members have types that conforms to PseudoEquatableType.")
return false
}
return zip(
childrenLhs.flatMap { $0.value as? PseudoEquatableType },
childrenRhs.flatMap { $0.value as? PseudoEquatableType })
.reduce(true) { $0 && $1.0.isEqual(to: $1.1) }
}
We setup some non-native types to use in the example:
struct MyStruct {
var myInt: Int = 0
var myString: String = ""
}
class MyClass {
var myInt: Int
var myString: String
var myStruct: MyStruct
var myColor: UIColor
init(myInt: Int, myString: String,
myStruct: MyStruct, myColor: UIColor) {
self.myInt = myInt
self.myString = myString
self.myStruct = myStruct
self.myColor = myColor
}
}
For some given type, e.g. MyClass
, the EquatableConstruct
"equatable wrapper" may be used only if all types of different properties in the type itself (here, in MyClass
) themselves conform to PseudoEquatableType
:
/* Extend (some/all) fundamental (equatable) Swift types to PseudoEquatableType */
extension Bool : PseudoEquatableType {}
extension Int : PseudoEquatableType {}
// ... Int8, UInt8, ..., Double, Float, ... and so on
extension String : PseudoEquatableType {}
extension UIColor: PseudoEquatableType {}
/* As a MyStruct instance is contained in MyClass, extend MyStruct to PseudoEquatableType
to add the type to allowed property types in EquatableConstruct */
extension MyStruct : PseudoEquatableType {}
/* Conformance to EquatableConstruct implies conformance to Equatable */
extension MyStruct : EquatableConstruct {}
extension MyClass : EquatableConstruct {}
Testing the automatic Equatable
conformance of MyStruct
and MyClass
, given by their conformance to EquatableConstruct
:
/* Example */
var aa = MyStruct()
var bb = MyStruct()
aa == bb // true
aa.myInt = 1
aa == bb // false
var a = MyClass(myInt: 10, myString: "foo",
myStruct: aa, myColor: UIColor(white: 1.0, alpha: 1.0))
var b = MyClass(myInt: 10, myString: "foo",
myStruct: aa, myColor: UIColor(white: 1.0, alpha: 1.0))
a == b // true
a.myInt = 2
a == b // false
b.myInt = 2
b.myString = "Foo"
a.myString = "Foo"
a == b // true
a.myStruct.myInt = 2
a == b // false
a.myStruct.myInt = 1
a == b // true
a.myColor = UIColor(white: 0.5, alpha: 1.0)
a == b // false