Swift: Copy-modify



It often happens that we need to copy an object, changing some of its properties, but keeping the rest unchanged. There is a function for this task copy().

This is an excerpt copy()from the Kotlin documentation for the method . In our native Swift language, this means something like this:



struct User {
    let id: Int
    let name: String
    let age: Int
}

let steve = User(id: 1, name: "Steve", age: 21)

//  ,   `name`  `age`
let steveJobs = steve.changing { newUser in
    newUser.name = "Steve Jobs"
    newUser.age = 41
}


Looks delicious, doesn't it?



, Swift " ". .



 



, var let?



struct User {
    let id: Int
    var name: String
    var age: Int
}

let steve = User(id: 1, name: "Steve", age: 21)

...

var steveJobs = steve

steveJobs.name = "Steve Jobs"
steveJobs.age = 41


:



  • , , , - .
  • "". , willSet didSet .
  • , .


, — , :



//   ,   `name`
let steveJobs = User(
    id: steve.id, 
    name: "Steve Jobs",
    age: steve.age
)


, , . - , .



, , "" , .







  • , . 
  • Changeable -, , .
  • , .






, , . Key-Path , Key-Path Dynamic Member Lookup Swift 5.1 .



, generic-:



@dynamicMemberLookup
struct ChangeableWrapper<Wrapped> {
    private let wrapped: Wrapped
    private var changes: [PartialKeyPath<Wrapped>: Any] = [:]

    init(_ wrapped: Wrapped) {
        self.wrapped = wrapped
    }

    subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
        get { 
            changes[keyPath].flatMap { $0 as? T } ?? wrapped[keyPath: keyPath] 
        }

        set {
            changes[keyPath] = newValue
        }
    }
}


, KeyPath. , , . .



changes[keyPath] as? T, T . nil, , . , flatMap(:), , changes .

@dynamicMemberLookup , , var.





Xcode . : .



Changeable



, , Changeable :



protocol Changeable {
    init(copy: ChangeableWrapper<Self>)
}

extension Changeable {
    func changing(_ change: (inout ChangeableWrapper<Self>) -> Void) -> Self {
        var copy = ChangeableWrapper<Self>(self)
        change(&copy)
        return Self(copy: copy)
    }
}


changing(:) , , .



, , Changeable:



extension User: Changeable {
    init(copy: ChangeableWrapper<Self>) {
        self.init(
            id: copy.id,
            name: copy.name,
            age: copy.age
        )
    }
}


, , — :



let steve = User(id: 1, name: "Steve", age: 21)

let steveJobs = steve.changing { newUser in
    newUser.name = "Steve Jobs"
    newUser.age = 30
}


, , …







changing(:) , , , :



struct Company {
    let name: String
    let country: String
}

struct User {
    let id: Int
    let company: Company
}

let user = User(
    id: 1, 
    company: Company(
        name: "NeXT", 
        country: "USA"
    )
)


user, company.name, :



let appleUser = user.changing { newUser in
    newUser.company = newUser.company.changing { newCompany in
        newCompany.name = "Apple"
    }
}


, .



. — ChangeableWrapper:



subscript<T: Changeable>(
    dynamicMember keyPath: KeyPath<Wrapped, T>
) -> ChangeableWrapper<T> {
    get {
        ChangeableWrapper<T>(self[dynamicMember: keyPath])
    }

    set { 
        self[dynamicMember: keyPath] = T(copy: newValue)
    }
}


, Changeable. Swift - . , .



, :



let appleUser = user.changing { newUser in
    newUser.company.name = "Apple"
}


, .







, , , , . Swift , .



. , , . Stencil- Sourcery, .



, , , Swift 5.1 .



 . . !




All Articles