【问题标题】:How to implement `isEqual` method conveniently using Mirror in Swift 3如何在 Swift 3 中使用 Mirror 方便地实现`isEqual`方法
【发布时间】:2016-11-02 17:13:22
【问题描述】:

喜欢

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

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

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

【问题讨论】:

  • 那些冒号...我的眼睛!!!

标签: swift foundation


【解决方案1】:

除了诊断之外,您不应使用运行时自省,当然也不应避免使用少量“样板”代码或避免更新现有代码。

然而,下面是一些关于该主题的 cmets,但请注意,这些应被视为 hack,不应在任何类型的生产代码中使用。但是,它们可以展示 Swift 中运行时自省的一些示例用法。

Equatable class/struct "wrapper",使用运行时内省进行逐个属性相等性测试

你可以实现一个Equatable-container 来保存相等类型的值,它可以(不同于Equatable 本身)被强制转换为,我们将使用它来比较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 等价的“包装器”及其与 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 { $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) }
}

示例用法

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

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“等价包装器”只有在类型本身(这里是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

【讨论】:

  • “这些应该被认为是 hack,不应该在任何类型的生产代码中使用”为什么?
  • @ScottyBlades 我已经发布了四年多,但如果我记得:它使用运行时内省来定义基于镜像的(伪)等价合约(那个,IIRC,当时主要用于游乐场和调试;这些天我正在关注 Swift 的状态)以一种以静态为荣的语言,而不是其极其动态的兄弟 Obj-C。我认为我的这个答案的第一句话已经给了你我的理由,虽然:“除了诊断之外,你不应该使用运行时内省,[...]”。
  • 谢谢。我想我认为通常的显式相等检查并不比使用镜像更静态。 Struct/Class 属性名称是静态设置的,mirror 只是动态识别它们……但话又说回来,如果开发人员在符合标准的结构或类中没有属性,则不会有静态检查或警告……
猜你喜欢
  • 2017-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-29
  • 2012-07-30
  • 2017-01-26
相关资源
最近更新 更多