修改深度嵌套结构的简单方法

时间:2020-09-13 00:01:50

标签: swift

我对Swift结构的“写时复制”行为越来越熟悉。我认为这是解决必须管理结构引用的一种很好的方法,但是在处理深度嵌套的结构时有点麻烦。

如果要更新深度嵌套的值,则需要该值的直接路径,以便可以在一行上进行修改:

myStruct.nestedArray[index].nestedValue = 1

编译器将复制myStruct.nestedArray[index]并在该新值上将nestedValue设置为1。然后它将复制myStruct.nestedArray并将新值设置为index。然后它将复制myStruct并用具有上述所有更改的新值替换以前的值。

这很好用,而且很酷,您只需一行代码即可完成操作,而不必担心之前引用过myStruct及其子级的任何内容。但是,如果解析值路径时涉及到更复杂的逻辑,则该逻辑将变得更加冗长:

struct MyStruct {
    var nestedEnum: MyEnum
}

enum MyEnum {
    case one([NestedStruct])
    case two([NestedStruct])
}

struct NestedStruct {
    var id: Int
    var nestedValue: Int
}

var myStruct = MyStruct(nestedEnum: .one([NestedStruct(id: 0, nestedValue: 0)]))
if case .one(var nestedArray) = myStruct.nestedEnum {
    if let index = nestedArray.firstIndex(where: { $0.id == 0 }) {
        nestedArray[index].nestedValue = 1
        myStruct.nestedEnum = .one(nestedArray)
    }
}

理想情况下,您可以执行以下操作:

if case .one(var nestedArray) = myStruct.nestedEnum {
    if var nestedStruct = nestedArray.first(where: { $0.id == 0 }) {
        nestedStruct.nestedValue = 1
    }
}

但是一旦设置了nestedStruct.nestedValue,就会吞噬nestedStruct的新值。

如果Swift可以在函数外部使用inout语义,那将是一个很好的选择,因此我可以先对nestedArray进行“引用”,然后对其中的nestedStruct进行设置内部nestedValue,导致副本传播到myStruct,其方式与我能够在一行中完成的方式相同。

有没有人有什么好方法可以处理深层嵌套的结构,从而可以在这里帮助我?还是我只需要忍受上面第二个示例中的模式?

1 个答案:

答案 0 :(得分:1)

我最终得到的解决方案非常针对 SwiftUI,但它可能适用于其他框架。

基本上,我没有用一个单一的顶级方法来负责深度更新结构,而是安排了我的 SwiftUI 层次结构来镜像结构的结构,并将 Binding 向下传递,它只管理结构的一个节点层次结构。

例如,鉴于我上面定义的结构:

struct MyStruct {
    var nestedEnum: MyEnum
}

enum MyEnum {
    case one([NestedStruct])
    case two([NestedStruct])
}

struct NestedStruct {
    var id: Int
    var nestedValue: Int
}

我可以这样做:

struct MyStructView: View {
    @Binding var myStruct: MyStruct

    var body: some View {
        switch myStruct.nestedEnum {
        case .one: OneView(array: oneBinding)
        case .two: TwoView(array: twoBinding)
        }
    }

    var oneBinding: Binding<[NestedStruct]> {
        .init(
            get: {
                if case .one(array) = myStruct.nestedEnum {
                     return array
                }
                fatalError()
            },
            set: { myStruct.nestedEnum = .one($0) }
        )
    }

    var twoBinding: Binding<[NestedStruct]> { /* basically the same */ }
}

struct OneView: View {
    @Binding var array: [NestedStruct]

    var body: some View {
        ForEach(0..<array.count, id: \.self) {
            NestedStructView(nestedStruct: getBinding($0))
        }
    }

    func getBinding(_ index: Int) -> Binding<NestedStruct> {
        .init(get: { array[index] }, set: { array[index] = $0 })
    }
}

struct NestedStructView: View {
    @Binding var nestedStruct: NestedStruct

    var body: some View {
        NumericInput(title: "ID: \(nestedStruct.id)", value: valueBinding)
    }

    var valueBinding: Binding<Int> {
        .init(get: { nestedStruct.value }, set: { nestedStruct.value = $0 })
    }
}

唯一令人讨厌的一点是手动构造 Binding 可能有点冗长。我希望 SwiftUI 有一些语法可以从包含数组或结构的 Binding 中获取嵌套的 Binding