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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 05:40:34