How to do "Deep Copy" in Swift?
Deep Copy
Your example is not a deep copy as discussed on StackOverflow. Getting a true deep copy of an object would often require NSKeyedArchiver
Swift and copying
The NSCopying
protocol is the Objective-C way of providing object copies because everything was a pointer and you needed a way of managing the generation of copies of arbitrary objects. For an arbitrary object copy in Swift you might provide a convenience initializer where you initialize MyObject another MyObject and in the init assign the values from the old object to the new object. Honestly, that is basically what -copy
does in Objective-C except that it usually has to call copy on each of the sub-objects since Objective-C practices defensive copying.
let object = MyObject()
let object2 = MyObject(object)
Almost everything is pass-by-value. Almost.
However, in Swift almost everything is pass-by-value (you should really click the aforementioned link) so the need for NSCopying is greatly diminished. Try this out in a Playground:
var array = [Int](count: 5, repeatedValue: 0)
print(unsafeAddressOf(array), terminator: "")
let newArray = array
print(unsafeAddressOf(newArray), terminator: "")
array[3] = 3
print(array)
print(newArray)
You can see that the assignment is not a copy of the pointer but actually a new array. For a truly well-written discussion of the issues surrounding Swift's non-copy-by-value semantics with relation to structs and classes I suggest the fabulous blog of Mike Ash.
Finally, if you want to hear everything you need to know from Apple you can watch the WWDC 2015 Value Semantics video. Everyone should watch this video, it really clears up the way memory is handled within Swift and how it differs from Objective-C.
If Foo
is an Objective-C class that implements NSCopying
, then the following will work:
var foo2 = foo.copy();
-copy
is not defined using property notation in Foundation, so you can't treat it as a property in Swift even though you can use dot notation in Objective-C. In fact, you really shouldn't use dot notation (even though it is syntactically legal) because -copy
is not logically a property of the object, it is a method taking no parameters that manufactures a copy of the object.
NB this is not a deep copy, just as it is not a deep copy in Objective-C unless the implementation also copies all the members of the Foo instance.
Based on previous answer here
As of now, Feb 2021, there is no proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
- Make your class conforms to codable
class Dog: Codable{
var breed:String = "JustAnyDog"
}
- Create this helper class
class DeepCopier {
//Used to expose generic
static func Copy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
- Call this method whenever you need true deep copy of your object, like this:
//Now suppose
let dog = Dog()
guard let clonedDog = DeepCopier.Copy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
clonedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our object. Just make sure all your Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.
To My Forgetful Future Self:
For anyone else looking for an easy way to do deep copy of a tree-style object with Swift (parent may/may not have children and those children may/may not have children & so on)…
If you have your classes set up for NSCoding (for persistent data between launches), I was able to use that ability to do a deep copy of any particular instance of this tree structure by doing the following…
(Swift 4)
class Foo: NSObject, NSCoding {
var title = ""
var children: [Foo] = []
*blah, blah, blah*
// MARK: NSCoding
override public func encode(with coder: NSCoder) {
super.encode(with: coder)
coder.encode(title as Any?, forKey: "title")
coder.encode(children as Any?, forKey: "children")
}
required public init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.title = decoder.decodeObject(forKey: "title") as? String ?? ""
self.children = decoder.decodeObject(forKey: "children") as? [Foo] ?? []
}
}
Meanwhile… Elsewhere…
// rootFoo is some instance of a class Foo that has an array of child Foos that each can have their own same array
// Archive the given instance
let archive = NSKeyedArchiver.archivedData(withRootObject: rootFoo)
// Unarchive into a new instance
if let newFoo = NSKeyedUnarchiver.unarchiveObject(with: archive) as? Foo {
// newFoo has identical copies of all the children, not references
}