Swift3中如何使用Mirror方便的实现`isEqual`方法

How to implement `isEqual` method conveniently using Mirror in Swift 3

喜欢

class A :NSObject {
    let a :Int
    let b :UIColor
}

我不想通过一一比较所有属性来实现 isEqual。如果那样,当我添加另一个 属性 时,我应该再次修改 isEqual 的工具。

在swift中使用Mirror时,我可以方便地打印所有属性。如何使用 Mirror.

方便地实现 isEqual 方法

除了诊断之外,您不应使用运行时内省,当然也不应避免使用少量 "boilerplate" 代码或避免更新现有代码。

然而,下面是关于该主题的一些评论,但请注意 这些应被视为 hack,不应在任何类型的生产代码中使用 。但是,他们可以在 Swift.

中展示运行时内省的一些示例用法

等式 class/struct "wrapper",使用运行时内省 属性-by-属性 等式测试

您可以实现一个 Equatable 容器来保存等值类型的值,它可以(不同于 Equatable 本身)被强制转换为,我们将利用它来比较 say 的属性, classstruct.

/*  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
    }
}

使用 class/struct 可等式 "wrapper" 及其与 Equatable 的一致性实现(ab)使用运行时自省:

/*  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 { [=11=].label != nil }
    let childrenRhs = mirrorRhs.children.filter { [=11=].label != nil }

    guard childrenLhs.count == childrenRhs.count else { return false }

    guard !childrenLhs.contains(where: { !([=11=].value is PseudoEquatableType) }) else {
        print("Invalid use: not all members have types that conforms to PseudoEquatableType.")
        return false
    }

    return zip(
        childrenLhs.flatMap { [=11=].value as? PseudoEquatableType },
        childrenRhs.flatMap { [=11=].value as? PseudoEquatableType })
        .reduce(true) { [=11=] && .0.isEqual(to: .1) }
}

用法示例

我们设置了一些非本机类型以在示例中使用:

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
    }
}

对于某些给定的类型,例如MyClassEquatableConstruct "equatable wrapper" 只有在类型本身(这里,MyClass 中)的所有类型的不同属性本身符合 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 {}

测试 MyStructMyClass 的自动 Equatable 一致性,由它们对 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