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

时间:2016-07-12 14:09:18

标签: swift immutability

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

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.如何在不构建新对象且必须为每个属性指定值的情况下执行此操作?

5 个答案:

答案 0 :(得分:15)

注意,虽然您使用常量 ab的占位符值,但您无法使用任何其他值构建A的实例,但这样占位符。写初始化器。您可以编写自定义方法来更改struct中的任何值:

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)

答案 1 :(得分: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{ $0.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

答案 2 :(得分:3)

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

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

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

struct A {
    let a: Int
    var b: Int
}

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

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

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

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

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

现在之前的示例更改为:

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

我看到向很多结构添加“更改”会很痛苦,但扩展会很棒:

protocol Changeable {}

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

使用“Changeable”扩展您的结构,您将拥有“change”功能。

答案 3 :(得分:0)

我真的很喜欢@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

答案 4 :(得分:0)

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

可选的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"}