How to do "Deep Copy" in Swift?
Solution 1
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.
Solution 2
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
}
Solution 3
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.
Solution 4
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.
Comments
-
allenlinli over 3 years
In Objective-C, one can deep-copy by following:
Foo *foo = [[Foo alloc] init]; Foo *foo2 = foo.copy;
How to do this deep-copy in Swift?
-
Cameron Lowell Palmer over 8 yearsYou refer specifically of course to Objective-C. [myObject copy] would be correct there, in Swift it would be myObject.copy()
-
HixField about 8 yearsIf your array contains objects (class instances), they are "by reference" and then copy the array will not copy these. You will just end up with 2 different arrays (arrays are "by value" so they are indeed copied) that point to the same objects.
-
Cameron Lowell Palmer about 8 years@HixField as covered in the 'almost everything' linked article.
-
Crashalot about 8 years@HixField this is a great note and should be highlighted in the answer. Swift newbies who do not read the linked article or read your comment may mistakenly assume objects would also get copied in the array.
-
Cameron Lowell Palmer about 8 years@Crashalot how is that for highlighting
-
Crashalot about 8 yearsLooks good, but to avoid inadvertently misleading Swift newbies, it would be ideal to explicitly incorporate @HixField's comment or some variation.
-
allenlinli almost 8 years@CameronLowellPalmer I tried "print(unsafeAddressOf(newArray), terminator: "")", but it gives warning said "Argument type (int) does not conform to expected type 'AnyObject'". It's wried.
-
Cameron Lowell Palmer almost 8 years@AllenLin is it? Are you passing an int or an array?
-
Cameron Lowell Palmer almost 8 years@AllenLin try importing Foundation.
-
allenlinli almost 8 years@CameronLowellPalmer Yeah, it worked. Thanks for answer newbie mistake.
-
bio over 6 yearsthis should actually be the proper answer to the OP. Using
copy()
and NSCopying is the proper way. For arrays or dictionaries you need to iterate over all elements and make copies yourself. -
Luke Rogers almost 6 yearsIf using Swift 4, why not implement the new
Codable
instead? -
Muhammad Nayab over 5 years@LukeRogers same here and I guess in case of Codable we dont have to provide implement "encode(with coder: NSCoder) or init?(coder decoder: NSCoder) "