Swift中如何实现对象间指定参数批量复制?
Swift结构体批量复制指定属性的实现方案
完全可以实现你要的批量复制指定属性的需求,利用Swift的KeyPath特性就能搞定。下面直接上代码:
首先是原有的结构体和枚举定义:
enum PetType: Int { case cat case dog } struct Pet { var id: String var name: String var age: Int var type: PetType }
然后给Pet写一个扩展,实现批量复制的方法:
extension Pet { mutating func copy(from other: Pet, valuesOfKeys keys: [PartialKeyPath<Pet>]) { for key in keys { // 根据属性类型匹配对应的可写KeyPath,完成赋值 if let stringKey = key as? WritableKeyPath<Pet, String> { self[keyPath: stringKey] = other[keyPath: stringKey] } else if let intKey = key as? WritableKeyPath<Pet, Int> { self[keyPath: intKey] = other[keyPath: intKey] } else if let typeKey = key as? WritableKeyPath<Pet, PetType> { self[keyPath: typeKey] = other[keyPath: typeKey] } } } }
测试一下效果:
var pet1 = Pet(id: "MyCat1", name: "Cookie", age: 4, type: .cat) let pet2 = Pet(id: "randomCatId", name: "Socks", age: 3, type: .dog) // 复制指定属性 pet1.copy(from: pet2, valuesOfKeys: [\.name, \.age]) // 此时pet1的属性为:id="MyCat1",name="Socks",age=3,type=.cat,完全符合预期
如果因为某些限制无法用上述方案,批量复制的最优方式是什么?
如果是属性类型特别多,不想一个个写类型判断,或者需要更通用的方案,可以做一个类型擦除的可写KeyPath包装器,这样不用关心属性类型:
// 通用的类型擦除包装器 struct AnyWritableKeyPath<Root> { private let getValue: (Root) -> Any private let setValue: (inout Root, Any) -> Void init<T>(_ keyPath: WritableKeyPath<Root, T>) { getValue = { $0[keyPath: keyPath] } setValue = { root, value in guard let typedValue = value as? T else { return } root[keyPath: keyPath] = typedValue } } } // 扩展Pet使用包装器的方法 extension Pet { mutating func copy(from other: Pet, valuesOfKeys keys: [AnyWritableKeyPath<Pet>]) { for key in keys { let value = key.getValue(other) key.setValue(&self, value) } } }
调用的时候稍微调整一下:
pet1.copy(from: pet2, valuesOfKeys: [AnyWritableKeyPath(\.name), AnyWritableKeyPath(\.age)])
另外,如果只是想创建一个新的Pet实例(不是修改现有实例),也可以直接写一个带默认值的构造器,批量传入要替换的属性,比如:
extension Pet { init(from original: Pet, name: String? = nil, age: Int? = nil, type: PetType? = nil, id: String? = nil) { self.id = id ?? original.id self.name = name ?? original.name self.age = age ?? original.age self.type = type ?? original.type } } // 使用方式: let pet3 = Pet(from: pet1, name: pet2.name, age: pet2.age)
这种方式代码更直观,适合属性数量固定且不多的场景。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

