Swift结构体存入NSCountedSet后无法正确执行减法运算的问题
解决NSCountedSet自定义结构体元素的减法问题
问题根源
你猜的没错——自定义的MyStruct实例默认不会被NSCountedSet判定为相等。因为NSCountedSet是Objective-C集合类型,依赖isEqual:和hash方法判断元素等价性:
- Swift结构体默认仅在所有属性完全相同且是同一实例时才被视为相等(未显式遵循
Equatable时,值类型的等价判断逻辑在Objective-C集合中不生效); - 每次创建
MyStruct("A")都是全新实例,NSCountedSet会将它们当作不同元素,导致count(for:)返回错误结果。
而String能正常工作,是因为Swift的String会桥接到Objective-C的NSString,后者已经实现了完善的isEqual:和hash逻辑,相同内容的字符串会被判定为等价。
解决方案
方案1:让自定义结构体支持Objective-C等价判断
通过让MyStruct遵循NSObject和Equatable,并实现isEqual:和hash方法,让NSCountedSet根据name属性判断元素是否相等:
@objcMembers struct MyStruct: NSObject, Equatable { let name: String init(_ name: String) { self.name = name } // 重写isEqual:,根据name判断等价性 override func isEqual(_ object: Any?) -> Bool { guard let other = object as? MyStruct else { return false } return self.name == other.name } // 重写hash,与isEqual:逻辑保持一致 override var hash: Int { return name.hash } }
修改后,你的原subtract函数就能正常工作:
- NSCountedSet会将所有
name相同的MyStruct实例视为同一元素; count(for:)会返回正确的计数,减法结果符合预期。
方案2:基于属性统计后构建结果集
如果不想修改MyStruct的继承关系,可以先将两个NSCountedSet转换为以name为键、计数为值的字典,完成减法后再重新构建NSCountedSet:
// 辅助函数:将NSCountedSet转换为[name: 计数]的字典 func nameCount(from set: NSCountedSet) -> [String: Int] { var countDict = [String: Int]() // 先收集所有唯一的MyStruct元素 let uniqueElements = set.compactMap { $0 as? MyStruct } .reduce(into: Set<MyStruct>()) { $0.insert($1) } for element in uniqueElements { countDict[element.name] = set.count(for: element) } return countDict } // 修改减法函数 func subtract(_ set1: NSCountedSet, _ set2: NSCountedSet) -> NSCountedSet { let dict1 = nameCount(from: set1) let dict2 = nameCount(from: set2) // 计算每个name的剩余计数 let resultCounts = dict1.mapValues { count1 in max(count1 - (dict2[$0.key] ?? 0), 0) }.filter { $0.value > 0 } // 构建结果NSCountedSet let result = NSCountedSet() for (name, count) in resultCounts { let element = MyStruct(name) (1...count).forEach { _ in result.add(element) } } return result }
这种方法绕开了NSCountedSet的等价判断逻辑,直接基于name属性统计计数,同样能得到预期结果。
内容的提问来源于stack exchange,提问作者koen
相关产品推荐
相关产品推荐

