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

后端 未结 4 830
日久生厌
日久生厌 2021-01-01 17:04

If I want to represent my view controller\'s state as a single struct and then implement an undo mechanism, how would I change, say, one property on the struct and, at the s

相关标签:
4条回答
  • 2021-01-01 17:15

    The best way I have found is to write an initializer method that takes an object of the same type to "copy", and then has optional parameters to set each individual property that you want to change.

    The optional init parameters allow you to skip any property that you want to remain unchanged from the original struct.

    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"}
    
    0 讨论(0)
  • 2021-01-01 17:24

    Note, that while you use placeholder values for constants a and b you are not able to construct instance of A with any other values but this placeholders. Write initializer instead. You may write custom method that change any value in struct also:

    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)
    
    0 讨论(0)
  • 2021-01-01 17:30

    If you can live with the properties being mutable, this is an alternative approach. Advantage is that it works for every struct and there's no need to change the function upon adding a property:

    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 }
    

    You could also have an extension for this:

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

    Also you can use this to do it without any additional code:

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

    And if you don't mind the new struct being mutable, it's just

    var nextState = state
    nextState.b = 4
    
    0 讨论(0)
  • 2021-01-01 17:32

    I really like @Shadow answer, but I had a hard time adapting it to a scenario were the fields of the struct could be nullable, so I decided to use the builder pattern instead. My code looks something like this:

    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
            }
        }
    }
    

    And then you can use it like:

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

    With this my structs are really immutable, and I can quickly create a copy and change one or many of its field to be either nil or another Int

    0 讨论(0)
提交回复
热议问题