You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: [])
    }
}

关键细节说明

  1. 类型校验:首先确保两个对象是同一类型,避免跨类型的无效比较
  2. 可选类型处理:通过unwrapOptional递归解开嵌套可选,比如String??这种情况也能正确比较
  3. 集合支持:自动处理数组和字典的比较,数组会逐个元素递归比较,字典会按key匹配后递归比较元素
  4. 忽略属性:支持传入属性名称数组,跳过不需要比较的属性(比如临时状态属性)
  5. 递归嵌套对象:如果属性是另一个自定义模型类,会自动递归调用比较方法,不用手动处理嵌套的isEqual

注意事项

  • 计算属性:默认会比较所有Mirror能获取到的属性,包括计算属性。如果只想比较存储属性,可以在遍历的时候添加判断:
    // 在遍历children时添加
    guard let valueMirror = Mirror(reflecting: lhsChild.value), valueMirror.displayStyle == .stored else {
        continue
    }
    
  • 性能:Mirror的性能比手动写isEqual要差一些,如果是对性能要求极高的场景(比如大量对象比较),手动写还是更优,但大部分业务场景下这个性能差异可以忽略。

内容的提问来源于stack exchange,提问作者Marcel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:54:29