swift 如何在复制结构的同时修改它的一个属性?

fv2wmkja  于 2023-01-04  发布在  Swift
关注(0)|答案(6)|浏览(214)

如果我想将视图控制器的状态表示为一个结构体,然后实现一个撤销机制,那么我应该如何改变(比如说)结构体上的一个属性,同时获得前一个状态的副本?

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。我怎样才能做到这一点,而不构造一个新的对象,也不必为每个属性指定一个值?

eiee3dmh

eiee3dmh1#

注意,当你为 constantsab使用占位符值时,除了这个占位符,你不能用任何其他值构造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)
dgiusagp

dgiusagp2#

这里的答案是荒谬的,特别是在struct的成员发生变化的情况下。
让我们了解一下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"

但是请记住,如果您计划更改struct中的成员,则必须将其标记为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

6gpjuf90

6gpjuf903#

如果你可以接受属性是可变的,这是一种替代方法,优点是它适用于每个结构体,并且在添加属性时不需要改变函数:

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
fwzugrvs

fwzugrvs4#

我真的很喜欢@Shadow answer,但是我很难将它应用到结构体的字段可以为空的场景中,所以我决定使用builder模式,我的代码如下所示:

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

ar5n3qh5

ar5n3qh55#

我发现的最好的方法是编写一个初始化器方法,该方法接受要“复制”的相同类型的对象,然后具有可选参数来设置要更改的每个单独的属性。
可选的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"}
zlhcx6iw

zlhcx6iw6#

你可以像在Kotlin中那样为你的结构体写“copy”函数

struct A {
   let a: Int
   let b: Int

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

let state = A(a: 3, b: 5)
let stateCopy = state.copy()
let stateCopy2 = state.copy(a: 4)
let stateCopy3 = stateCopy2.copy(b: 8)
let stateCopy4 = stateCopy2.copy(a:1, b: 2)

相关问题