Swift自定义结构体:字符串插值未调用正确自定义description问题
Swift中CustomStringConvertible条件扩展的调度问题
问题代码
import Foundation struct MySet<T: Hashable>: CustomStringConvertible { let set: Set<T> } extension MySet { var description: String { return "\(self.set)" } } extension MySet where T: Comparable { var description: String { return "\(self.set.sorted())" } } let intSet: Set<Int> = [6, 3, 4, 9, 12, 49, -1, 44, -1000, 1000] let myIntSet = MySet(set: intSet) print("myIntSet: \(myIntSet.description)") print("myIntSet: \(myIntSet)") print("myIntSet: ", String(describing: myIntSet))
输出结果
myIntSet: [-1000, -1, 3, 4, 6, 9, 12, 44, 49, 1000] myIntSet: [9, -1, 3, 1000, 6, 4, 12, 44, 49, -1000] myIntSet: [9, -1, 3, 1000, 6, 4, 12, 44, 49, -1000]
问题原因
这是Swift方法调度机制导致的:
- 直接调用
myIntSet.description时,属于静态派发,编译器会根据myIntSet的具体类型(MySet<Int>,其中Int遵循Comparable),匹配到条件扩展中的description实现。 - 字符串插值
\(myIntSet)和String(describing: myIntSet)本质是通过CustomStringConvertible协议类型调用description,此时采用动态派发。由于你在MySet的无条件扩展中实现了description,该实现会被视为协议的“具体类型实现”,优先级高于条件扩展中的版本,因此会调用未排序的默认实现。
解决方法
有两种可行的修复方式:
方式一:将基础实现改为协议默认扩展
把description的基础实现放到CustomStringConvertible的协议扩展中,让条件扩展的实现覆盖默认值:
import Foundation struct MySet<T: Hashable>: CustomStringConvertible { let set: Set<T> } // 为MySet提供默认的description实现 extension CustomStringConvertible where Self: MySet { var description: String { return "\(self.set)" } } // 为元素遵循Comparable的MySet提供排序后的description extension CustomStringConvertible where Self: MySet, Self.Element: Comparable { var description: String { return "\(self.set.sorted())" } } // 声明MySet的Element关联类型 extension MySet { typealias Element = T } let intSet: Set<Int> = [6, 3, 4, 9, 12, 49, -1, 44, -1000, 1000] let myIntSet = MySet(set: intSet) print("myIntSet: \(myIntSet.description)") print("myIntSet: \(myIntSet)") print("myIntSet: ", String(describing: myIntSet))
方式二:在主结构体中统一处理条件判断
直接在MySet的description属性内,通过类型检查判断元素是否遵循Comparable,返回对应结果:
import Foundation struct MySet<T: Hashable>: CustomStringConvertible { let set: Set<T> var description: String { if let comparableElements = set as? Set<some Comparable> { return "\(comparableElements.sorted())" } return "\(set)" } } let intSet: Set<Int> = [6, 3, 4, 9, 12, 49, -1, 44, -1000, 1000] let myIntSet = MySet(set: intSet) print("myIntSet: \(myIntSet.description)") print("myIntSet: \(myIntSet)") print("myIntSet: ", String(describing: myIntSet))
两种方式都能让字符串插值和String(describing:)调用正确的description实现,输出排序后的结果。
内容的提问来源于stack exchange,提问作者Rick Clark
相关产品推荐
相关产品推荐

