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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:43:14