Swift中如何实现通用的类实例属性比较方法?
嘿,手动写isEqual确实是个重复又容易出错的活儿,用Mirror来做通用属性比较绝对是个聪明的思路!我来帮你完善这个方法,还会加上一些实用的细节处理,比如可选类型、嵌套对象甚至集合类型的比较。
完整的通用比较Helper实现
首先我们写一个工具类,包含核心的比较逻辑,还有辅助的可选解包方法:
class ObjectComparisonHelper { // 通用比较方法,支持可选类型、嵌套对象、集合类型 static func compareTwoObjects(_ lhs: Any, _ rhs: Any, ignoreProperties: [String] = []) -> Bool { // 第一步:判断两个对象是否为同一类型,不同直接返回false guard type(of: lhs) == type(of: rhs) else { return false } let lhsMirror = Mirror(reflecting: lhs) let rhsMirror = Mirror(reflecting: rhs) // 检查属性数量是否一致,不一致直接不相等 guard lhsMirror.children.count == rhsMirror.children.count else { return false } // 转换为数组方便按顺序遍历属性 let lhsChildren = Array(lhsMirror.children) let rhsChildren = Array(rhsMirror.children) for index in 0..<lhsChildren.count { let lhsChild = lhsChildren[index] let rhsChild = rhsChildren[index] // 跳过需要忽略的属性 guard let propertyName = lhsChild.label, !ignoreProperties.contains(propertyName) else { continue } // 确保属性名称一致(同一类的话应该没问题,但保险起见加上) guard rhsChild.label == propertyName else { return false } let lhsValue = lhsChild.value let rhsValue = rhsChild.value // 解开可选类型的包装(包括嵌套可选,比如String??) let unwrappedLhs = unwrapOptional(lhsValue) let unwrappedRhs = unwrapOptional(rhsValue) // 一个是可选值一个不是,直接不相等 if (unwrappedLhs == nil) != (unwrappedRhs == nil) { return false } // 两个都是nil,跳过这个属性继续比较 guard let lhsUnwrapped = unwrappedLhs, let rhsUnwrapped = unwrappedRhs else { continue } // 分情况处理不同类型的属性值 switch (lhsUnwrapped, rhsUnwrapped) { // 处理数组集合 case let (lhsArray as [Any], rhsArray as [Any]): guard lhsArray.count == rhsArray.count else { return false } for (elemLhs, elemRhs) in zip(lhsArray, rhsArray) { if !compareTwoObjects(elemLhs, elemRhs, ignoreProperties: ignoreProperties) { return false } } // 处理字典集合 case let (lhsDict as [AnyHashable: Any], rhsDict as [AnyHashable: Any]): guard lhsDict.count == rhsDict.count else { return false } for (key, lhsElem) in lhsDict { guard let rhsElem = rhsDict[key], compareTwoObjects(lhsElem, rhsElem, ignoreProperties: ignoreProperties) else { return false } } // 处理遵守Equatable的基础类型(String、Int、Bool等) case let (lhsComparable as Equatable, rhsComparable as Equatable): if lhsComparable != rhsComparable { return false } // 处理自定义对象,递归调用比较方法 default: if !compareTwoObjects(lhsUnwrapped, rhsUnwrapped, ignoreProperties: ignoreProperties) { return false } } } // 所有属性都匹配,返回true return true } // 辅助方法:递归解开可选类型的包装 private static func unwrapOptional(_ value: Any) -> Any? { let mirror = Mirror(reflecting: value) // 如果是可选类型,取它的唯一子元素继续解包 guard mirror.displayStyle == .optional, let child = mirror.children.first else { return value } return unwrapOptional(child.value) } }
在模型类中使用
现在你的BankDataModel里的isEqual可以简化成这样,还能指定忽略某些不需要比较的属性:
class BankDataModel: NSObject { // 你的属性定义... var accountHolderName: String? var accountNumber: String? var accountHolderThirdPartyAccountOwner: Bool = false var bankName: String? var bankRoutingNumber: String? var bic: String? var iban: String? var directDebitStatus: Int = 0 var sepaCreditorID: String? var sepaMandateID: String? override func isEqual(_ object: Any?) -> Bool { guard let rhs = object as? BankDataModel else { return false } // 如果有不需要比较的属性,比如某个临时计算属性,可以加在ignoreProperties里 return ObjectComparisonHelper.compareTwoObjects(self, rhs, ignoreProperties: []) } }
关键细节说明
- 类型校验:首先确保两个对象是同一类型,避免跨类型的无效比较
- 可选类型处理:通过
unwrapOptional递归解开嵌套可选,比如String??这种情况也能正确比较 - 集合支持:自动处理数组和字典的比较,数组会逐个元素递归比较,字典会按key匹配后递归比较元素
- 忽略属性:支持传入属性名称数组,跳过不需要比较的属性(比如临时状态属性)
- 递归嵌套对象:如果属性是另一个自定义模型类,会自动递归调用比较方法,不用手动处理嵌套的
isEqual
注意事项
- 计算属性:默认会比较所有
Mirror能获取到的属性,包括计算属性。如果只想比较存储属性,可以在遍历的时候添加判断:// 在遍历children时添加 guard let valueMirror = Mirror(reflecting: lhsChild.value), valueMirror.displayStyle == .stored else { continue } - 性能:
Mirror的性能比手动写isEqual要差一些,如果是对性能要求极高的场景(比如大量对象比较),手动写还是更优,但大部分业务场景下这个性能差异可以忽略。
内容的提问来源于stack exchange,提问作者Marcel
相关产品推荐
相关产品推荐

