如果我想将视图控制器的状态表示为单个结构,然后实现撤消机制,我该如何更改结构上的一个属性,同时获取前一个状态的副本?
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。如何才能做到这一点而不构造新对象并且不必为每个属性指定一个值?
这里的答案很荒谬,尤其是在结构体成员发生变化的情况下。
让我们了解 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"
我发现向很多结构添加“更改”会很痛苦,但扩展会很棒:
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”扩展你的结构,你将拥有“改变”的功能。
使用“更改”函数方法时,您在“更改”函数的调用站点中指定的任何属性(即类型为
WritableKeyPath
)都应在原始结构中标记为 var
,而不是 let
。
请注意,当您使用 constants
a
和 b
的占位符值时,您无法使用除此占位符之外的任何其他值构造 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{ $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
我真的很喜欢@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
您可以像 Kotlin 中一样为您的结构编写“复制”函数
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)
我发现的最好方法(将引用保留为
let
,而不是将它们更改为var
),是编写一个初始化方法,该方法采用相同类型的对象进行“复制”,然后用于设置您想要更改的每个单独属性的可选参数。
可选的
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 5.9 开始,引入了 Variadic Types,我们可以调整 @arutyun-enfendzhyan 的答案以允许一次进行多个更改:
struct Test {
// If the values can change, they should be declared as vars
var string = String()
var int = Int()
var double = Double()
}
struct KeyValuePair<Model, Value> {
let key: WritableKeyPath<Model, Value>
let value: Value
func update(_ model: inout Model) {
model[keyPath: key] = value
}
}
extension Test {
func copy<each Value>(
with pair: repeat KeyValuePair<Self, each Value>
) -> Self {
var clone = self
repeat (each pair).update(&clone)
return clone
}
}
let test = Test()
test.copy(with:
.init(key: \.string, value: "string"),
.init(key: \.int, value: 10),
.init(key: \.double, value: 20)
)
>> Test(string: "string", int: 10, double: 20.0)
extension Test {
func copy<each Value>(
keys: repeat WritableKeyPath<Self, each Value>,
values: repeat each Value
) -> Self {
var clone = self
repeat KeyValuePair(key: each keys, value: each values).update(model: &clone)
return clone
}
}
let test = Test()
test.copy(keys: \.string, \.int, \.double, values: "string", 10, 20.0)
>> Test(string: "string", int: 10, double: 20.0)
extension Test {
func copy<each Value>(
with tuple: repeat (key: WritableKeyPath<Self, each Value>, value: each Value)
) -> Self {
var clone = self
repeat KeyValuePair(key: (each tuple).key, value: (each tuple).value).update(model: &clone)
return clone
}
}
let test = Test()
test.copy(with: (\.string, "string"), (\.int, 10), (\.double, 20.0))
>> Test(string: "string", int: 10, double: 20.0)
Changeable
协议...如果我们能够像 @arutyun-enfendzhyan 那样将所有这些添加到协议中,那就太棒了。但可变参数类型和协议扩展似乎存在限制。
我们可以看到错误如下:
protocol Changeable {}
extension Changeable {
func copy<each Value>(
with pair: repeat KeyValuePair<Self, each Value>
) -> Self {
var clone = self
repeat (each pair).update(&clone) // Type of expression is ambiguous without more context
return clone
}
}
这很遗憾,因为我们需要复制并粘贴一些代码,而不是通过简单的协议重用它。
如果有人对如何使用此处的协议有其他想法,请随时添加评论!
从 Swift 5.9 和 Xcode 15 的正式版本开始,这似乎在 xcode 项目中工作,但仍然使我的 Playground 崩溃。 完整代码:
import Foundation
protocol Changeable {}
extension Changeable {
func copy<each Value>(
with pair: repeat KeyValuePair<Self, each Value>
) -> Self {
var clone = self
repeat (each pair).update(&clone)
return clone
}
}
struct KeyValuePair<Model, Value> {
let key: WritableKeyPath<Model, Value>
let value: Value
func update(_ model: inout Model) {
model[keyPath: key] = value
}
}
struct Test: Changeable {
var string = String()
var int = Int()
var double = Double()
}
let test = Test()
test.copy(with:
.init(key: \.double, value: 1),
.init(key: \.int, value: 2),
.init(key: \.string, value: "3")
)
>> Test(string: "3", int: 2, double: 1.0)