Zonily Jame
Zonily Jame

Reputation: 5349

Assign a variable by value, not by reference

What is the best way to make a copy of a class, not a struct variable without making a reference to it's memory location.

For example

    class Person {
        var name: String = ""
        var age: Int = 0

        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }

        var mapped: [String: Any] {
            return [
                "name": self.name,
                "age": self.age
            ]
        }
    }

    var person: Person = Person(name: "Hitchhiker", age: 42)

    var personCopy = person

    var dictionary: [String: Any] {
        return [
            "a_person": person.mapped,

            "c_personCopy": personCopy.mapped
        ]
    }

    print("Original:", dictionary.jsonStringValue)

    person.age = 100
    person.name = "Guide"

    print("\n\n========\n\n")
    print("Edit 1:", dictionary.jsonStringValue)

    personCopy.age = 200
    personCopy.name = "To the Galaxy"

    print("\n\n========\n\n")
    print("Edit 2:", dictionary.jsonStringValue)

// I have this swift extension to make my logs better, don't mind the implicit unwrapping.
extension Dictionary {
    var jsonStringValue: String {
        let jsonData = try! JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
        return String(data: jsonData, encoding: String.Encoding.utf8)!
    }
}

This prints an output which looks like this.

Original: {
  "a_person" : {
    "name" : "Hitchhiker",
    "age" : 42
  },
  "c_personCopy" : {
    "name" : "Hitchhiker",
    "age" : 42
  }
}


========


Edit 1: {
  "a_person" : {
    "name" : "Guide",
    "age" : 100
  },
  "c_personCopy" : {
    "name" : "Guide",
    "age" : 100
  }
}


========


Edit 2: {
  "a_person" : {
    "name" : "To the Galaxy",
    "age" : 200
  },
  "c_personCopy" : {
    "name" : "To the Galaxy",
    "age" : 200
  }
}

If I manipulate the values of the copies, the originals will also be, updated because the copies are assigned by reference.

I know I can create something like an extension function which makes a copy of the original variable, like this.

extension Person {
    func copy() -> Person {
        return Person(name: self.name, age: self.age)
    }
}

var person = Person(name: "Jon Snow", age: 0)
var personCopy = person.copy()
personCopy.name = "Jon Targaryen" // this is now a value copy.

But how can I do this easier without creating a lot of boilerplate code for a lot of different models?

Update:

I know that I can use Swift Protocols here, for example

protocol Copying {
    init(original: Self)
}

extension Copying {
    func copy() -> Self {
        return Self.init(original: self)
    }
}

Which I saw in this answer, but I'd have to subclass my Model classes to these, which may cause some problems since my Model is already a subclass and it wants me to implement some of these boilerplate initializers and I don't want that

Upvotes: 2

Views: 1969

Answers (1)

rmaddy
rmaddy

Reputation: 318774

Classes are reference types. The only way to get a copy is to define a copy method as you have or to create an init method that takes another instance to be copied (which is essentially the same as your copy method).

You really should consider making your model classes as struct instead of class as long as you don't need inheritance. Then you get the copy semantics automatically.

Upvotes: 2

Related Questions