简体   繁体   中英

In swift how to copy a struct with one with only one different field

If I have a swift struct

struct Person {
  let firstName: String
  let lastName: String
  let gender: Gender
  let height: Float
  let weight: Float
  let age: Int
}

If have an object a of type Person , from time to time, only one of the field changed, like lastName when the person gets married, age when the person had a birthday etc. but I don't want to create a mutating func for each of the fields, nor do I want to write a lot of boilerplate to construct a new struct passing in all those fields again. Is there a better to to achieve something like

let newPerson = oldPerson.copy(lastName = "Married")

I know this is a really neat feature in scala with case classes, but I have yet to see this feature used in swift. May I know how can I achieve this? Do you guys think it should be a baked in feature in swift? Maybe we can make a swift evolution proposal?

I don't know if there is an elegant solution. One option would be to define a copy method which takes optional parameters for each property, which default to the current property values:

struct Person {
    let firstName: String
    let lastName: String
    let age: Int

    func copy(firstName: String? = nil, lastName: String? = nil, age: Int? = nil) -> Person {
        return Person(firstName: firstName ?? self.firstName,
                      lastName: lastName ?? self.lastName,
                      age: age ?? self.age)
    }
}


let john = Person(firstName: "John", lastName: "Doe", age: 29)
let jane = john.copy(firstName: "Jane")
print(jane) // Person(firstName: "Jane", lastName: "Doe", age: 29)

A copy function of Person can naturally have default value for its arguments, which would allow calling it with only a subset of the properties of person. The problem is that we may not use self to refer to what values we'd like to use as default. We can, however, refer to static members of a class (fir convenience, say static members of Person ), which would allow us to create something along the lines

struct Person {
    static var currentObjFirstName: String = ""
    static var currentObjLastName: String? = ""
    // ...
    static var currentObjAge: Int = 0

    let firstName: String
    let lastName: String?
    // ...
    let age: Int

    init(firstName: String, lastName: String?, age: Int) {
        self.firstName = firstName
        self.lastName = lastName
        self.age = age
    }

    func prepareCopy() -> Person {
        Person.currentObjFirstName = self.firstName
        Person.currentObjLastName = self.lastName
        Person.currentObjAge = self.age
        return self
    }  

    func copy(firstName firstName: String = Person.currentObjFirstName, 
              lastName: String? = Person.currentObjLastName,
              age: Int = Person.currentObjAge) -> Person {
        return Person(firstName: firstName, lastName: lastName, age: age)              
    }  

}

Example usage:

let foo = Person(firstName: "foo", lastName: "bar", age: 42)
let bar = foo.prepareCopy().copy(age: 21)
print(bar) // Person(firstName: "foo", lastName: Optional("bar"), age: 21)
let baz = foo.prepareCopy().copy(lastName: nil)
print(baz) // Person(firstName: "foo", lastName: nil, age: 42)

Possibly this solution goes under your description as "... boilerplate to construct a new struct passing in all those fields again" , though, and apart from that, prepareCopy() above isn't very pretty...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM