How to do "Deep Copy" in Swift?

40,654

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.

  1. Make your class conforms to codable
class Dog: Codable{
    var breed:String = "JustAnyDog"
}
  1. 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
       }
    }
}
  1. 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.

Share:
40,654
allenlinli
Author by

allenlinli

Swifter Swift!

Updated on February 09, 2021

Comments

  • allenlinli
    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
    Cameron Lowell Palmer over 8 years
    You refer specifically of course to Objective-C. [myObject copy] would be correct there, in Swift it would be myObject.copy()
  • HixField
    HixField about 8 years
    If 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
    Cameron Lowell Palmer about 8 years
    @HixField as covered in the 'almost everything' linked article.
  • Crashalot
    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
    Cameron Lowell Palmer about 8 years
    @Crashalot how is that for highlighting
  • Crashalot
    Crashalot about 8 years
    Looks good, but to avoid inadvertently misleading Swift newbies, it would be ideal to explicitly incorporate @HixField's comment or some variation.
  • allenlinli
    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
    Cameron Lowell Palmer almost 8 years
    @AllenLin is it? Are you passing an int or an array?
  • Cameron Lowell Palmer
    Cameron Lowell Palmer almost 8 years
    @AllenLin try importing Foundation.
  • allenlinli
    allenlinli almost 8 years
    @CameronLowellPalmer Yeah, it worked. Thanks for answer newbie mistake.
  • bio
    bio over 6 years
    this 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
    Luke Rogers almost 6 years
    If using Swift 4, why not implement the new Codable instead?
  • Muhammad Nayab
    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) "