Swift Equatable子类!=调用父类==实现的原因
问题复现
原有代码实现:
class Base: Equatable { static func == (lhs: Base, rhs: Base) -> Bool { lhs.id == rhs.id } let id: String init(id: String) { self.id = id } } class SubClass: Base { public var id2: String? public init(id1: String, id2: String? = nil) { self.id2 = id2 super.init(id: id1) } static func == (lhs: SubClass, rhs: SubClass) -> Bool { lhs.id2 == rhs.id2 && lhs.id == rhs.id } }
测试代码与异常表现:
// a、b均为SubClass类型的实例 print(a != b) // 输出false,实际调用Base类的==实现 print(a == b) // 输出false,实际调用SubClass类的==实现
相同类型的两个实例,调用==和!=时走了不同层级的相等判断逻辑,不符合预期。
问题原因
- Swift中
!=不需要遵守Equatable的类型单独实现,标准库提供了全局泛型默认实现:static func != <T: Equatable>(lhs: T, rhs: T) -> Bool { return !(lhs == rhs) },这个实现走静态调度,不会动态识别子类的重载方法。 - 子类中实现的
==(lhs: SubClass, rhs: SubClass)是方法重载而非方法重写:基类==的入参类型为Base,子类同名方法入参为SubClass,没有标注override,不会被纳入基类的动态派发列表。 - 调用
==时,若变量声明类型为SubClass,编译器可静态匹配到子类的重载版本;调用!=时,编译器直接绑定标准库的泛型实现,该实现基于Base类型的Equatable协议遵循关系静态调用基类==,不会触发子类的重载逻辑。
修复方案
将相等判断逻辑改为支持动态派发的实例方法,基类的==统一调用该实例方法,子类重写实例方法即可实现全运算符的一致调度:
class Base: Equatable { static func == (lhs: Base, rhs: Base) -> Bool { lhs.isEqual(to: rhs) } let id: String init(id: String) { self.id = id } /// 可被子类重写的相等判断入口,支持动态派发 func isEqual(to other: Base) -> Bool { return self.id == other.id } } class SubClass: Base { public var id2: String? public init(id1: String, id2: String? = nil) { self.id2 = id2 super.init(id: id1) } override func isEqual(to other: Base) -> Bool { guard let otherSub = other as? SubClass else { return false } return self.id == otherSub.id && self.id2 == otherSub.id2 } }
修复后无论调用==还是!=,都会通过动态派发到实际类型对应的isEqual实现,不会出现调度不一致的问题,子类也不需要单独编写static func ==的重载逻辑。
内容的提问来源于stack exchange,提问作者Johnny
相关产品推荐
相关产品推荐

