如何复制结构并同时修改其属性之一?

How to copy a struct and modify one of its properties at the same time?

如果我想将我的视图控制器的状态表示为单个结构,然后实现撤消机制,我将如何更改结构上的一个 属性,同时获得一个先前状态的副本?

struct A {
   let a: Int
   let b: Int

   init(a: Int = 2, b: Int = 3) {
      self.a = a
      self.b = b
   }
}

let state = A()

现在我想要一个 state 的副本,但 b = 4。如何在不构造新对象并且不必为每个 属性 指定值的情况下执行此操作?

请注意,当您使用 常量 ab 的占位符值时,您无法构造 A 的实例除此占位符之外的任何其他值。改为编写初始化程序。您也可以编写自定义方法来更改结构中的任何值:

struct A {
    let a: Int
    let b: Int

    init(a: Int = 2, b: Int = 3) {
        self.a = a
        self.b = b
    }

    func changeValues(a: Int? = nil, b: Int? = nil) -> A {
        return A(a: a ?? self.a, b: b ?? self.b)
    }
}

let state = A()
let state2 = state.changeValues(b: 4)

如果您可以接受属性的可变性,这是另一种方法。优点是它适用于每个结构,并且在添加 属性:

时无需更改函数
struct A {
    var a: Int
    var b: Int

    func changing(change: (inout A) -> Void) -> A {
        var a = self
        change(&a)
        return a
    }
}

let state = A(a: 2, b: 3)

let nextState = state.changing{ [=10=].b = 4 }

你也可以为此添加一个扩展:

protocol Changeable {}
extension Changeable {
    func changing(change: (inout Self) -> Void) -> Self {
        var a = self
        change(&a)
        return a
    }
}

extension A : Changeable {}

您也可以使用它来完成它而无需任何额外的代码:

let nextState = {
    var a = state
    a.b = 4
    return a
}()

如果你不介意新结构是可变的,它只是

var nextState = state
nextState.b = 4

我真的很喜欢@Shadow 的回答,但是我很难让它适应结构的字段可以为空的场景,所以我决定改用构建器模式。我的代码看起来像这样:

struct A {
    let a: Int?
    let b: Int?

    class Builder {
        private var a: Int?
        private var b: Int?

        init(struct: A) {
            self.a = struct.a
            self.b = struct.b
        }

        func build() -> A {
            return A(a: self.a, b: self.b)
        }

        func withA(_ a: Int?) -> Builder {
            self.a = a
            return self
        }

        func withB(_ b: Int?) -> Builder {
            self.b = b
            return self
        }
    }
}

然后你可以像这样使用它:

A.Builder(struct: myA).withA(a).withB(nil).build()

有了这个我的结构真的是不可变的,我可以快速创建一个副本并将其一个或多个字段更改为 nil 或另一个 Int

我发现的最佳方法是编写一个初始化方法,该方法采用相同类型的对象进行“复制”,然后使用可选参数来设置要更改的每个 属性。

可选的 init 参数允许您跳过任何您希望与原始结构保持不变的 属性。

struct Model {
    let a: String
    let b: String
    
    init(a: String, b: String) {
        self.a = a
        self.b = b
    }
    
    init(model: Model, a: String? = nil, b: String? = nil) {
        self.a = a ?? model.a
        self.b = b ?? model.b
    }
}

let model1 = Model(a: "foo", b: "bar")
let model2 = Model(model: model1, b: "baz")

// Output:
// model1: {a:"foo", b:"bar"}
// model2: {a:"foo", b:"baz"}

这里的答案很荒谬,尤其是在结构的成员发生变化的情况下。

让我们了解 Swift 的工作原理。

当一个结构从一个变量设置为另一个变量时,该结构会自动克隆到新变量中,即相同的结构彼此不相关。

struct A {
    let x: Int
    var y: Int
}

let a = A(x: 5, y: 10)
var a1 = a
a1.y = 69
print("a.y = \(a.y); a1.y = \(a1.y)") // prints "a.y = 10; a1.y = 69"

请记住,如果您打算更改结构中的成员,则必须将它们标记为 var,而不是 let

更多信息在这里:https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

很好,但是如果您仍然想在一行中复制和修改,请将此函数添加到您的结构中:

func changing<T>(path: WritableKeyPath<A, T>, to value: T) -> A {
    var clone = self
    clone[keyPath: path] = value
    return clone
}

现在把之前的例子改成这样:

let a = A(x: 5, y: 10)
let a1 = a.changing(path: \.y, to: 69)
print("a.y = \(a.y); a1.y = \(a1.y)") // prints "a.y = 10; a1.y = 69"

我看到将 'changing' 添加到很多结构中会很痛苦,但是扩展会很棒:

protocol Changeable {}

extension Changeable {
    func changing<T>(path: WritableKeyPath<Self, T>, to value: T) -> Self {
        var clone = self
        clone[keyPath: path] = value
        return clone
    }
}

使用 'Changeable' 扩展您的结构,您将拥有 'changing' 功能。

对于 'changing' 函数方法,您在 'changing' 函数的调用站点(即 WritableKeyPath 类型)中指定的任何 属性 都应标记在原始结构为 var,而不是 let.