Deep Copy for Array of Objects in Swift

how to make a deep copy of a swift array of class objects

As a simple statement, you could use code like this:

var copiedArray = array.map{$0.copy()}

Note that the term "deepCopy" is a little misleading for what you're talking about. What if the array is heterogeneous and contains other containers like arrays, dictionaries, sets, and other custom container and "leaf" objects? What you should really do is to create a protocol DeepCopiable and make it a requirement that any object that conforms to DeepCopiable require that any child objects also conform to the DeepCopiable protocol, and write the deepCopy() method to recursively call deepCopy() on all child objects. That way you wind up with a deep copy that works at any arbitrary depth.

deep copy for array of objects in swift

Since ordered is a swift array, the statement

 var orderedCopy = ordered

will effectively make a copy of the original array.

However, since Meal is a class, the new array will contain references
to the same meals referred in the original one.

If you want to copy the meals content too, so that changing a meal in one array will not change a meal in the other array, then you must define Meal as a struct, not as a class:

struct Meal { 
...

From the Apple book:

Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they are passed around in your code, but classes are passed by reference.

How does an array in swift deep copy itself when copied or assigned

Assignment of any struct (such as Array) causes a shallow copy of the structure contents. There's no special behavior for Array. The buffer that stores the Array's elements is not actually part of the structure. A pointer to that buffer, stored on the heap, is part of the Array structure, meaning that upon assignment, the buffer pointer is copied, but it still points to the same buffer.

All mutating operations on Array do a check to see if the buffer is uniquely referenced. If so, then the algorithm proceeds. Otherwise, a copy of the buffer is made, and the pointer to the new buffer is saved to that Array instance, then the algorithm proceeds as previously. This is called Copy on Write (CoW). Notice that it's not an automatic feature of all value types. It is merely a manually implemented feature of a few standard library types (like Array, Set, Dictionary, String, and others). You could even implement it yourself for your own types.

When CoW occurs, it does not do any deep copying. It will copy values, which means:

  • In the case of value types (struct, enum, tuples), the values are the struct/enum/tuples themselves. In this case, a deep and shallow copy are the same thing.
  • In the case of reference types (class), the value being copied is the reference. The referenced object is not copied. The same object is pointed to by both the old and copied reference. Thus, it's a shallow copy.

Deep copy Array where Element is Optional of custom class

It seems like you have bit of incorrect assumption for array extension of Optional Elements. You are calling copy on optional which you have not declared. Just make sure that Optional also conform to Copyable and it should work fine with clone on optional array.

extension Optional: Copyable where Wrapped: Copyable {
init(instance: Optional<Wrapped>) {
if let instance = instance {
self = Optional(instance.copy())
} else {
self = nil
}
}
}

Altogether, your code would look something like this,

protocol Copyable {
init(instance: Self)
}

extension Copyable {
func copy() -> Self {
return Self.init(instance: self)
}
}

extension Optional: Copyable where Wrapped: Copyable {
init(instance: Optional<Wrapped>) {
if let instance = instance {
self = Optional(instance.copy())
} else {
self = nil
}
}
}

extension Array where Element: Copyable {
func clone() -> [Element] {
var copiedArray = [Element]()
for element in self {
copiedArray.append(element.copy())
}
return copiedArray
}
}

class MySimpleObject: Copyable {

let myString: String

init() {
self.myString = ""
}

required init(instance: MySimpleObject) {
self.myString = instance.myString
}
}

class MyObject: Copyable {

var myArray = [MySimpleObject]()
var myArrayWithOptionals = [MySimpleObject?]()

required init(instance: MyObject) {
self.myArray = instance.myArray.clone()
self.myArrayWithOptionals = instance.myArrayWithOptionals.clone()
}
}

Swift 4 Copy arrays by value

If the type within your array is an enum or a struct, no problem. You don't need to think about it:

var a = [1,2,3]
var b = a
b[1] = 3
b // [1, 3, 3]
a // [1, 2, 3]

If your array contains a class then you do need to think about how you copy objects, because by default you will be working with pointers to the same instances:

let ns = NSMutableArray()
var arr = [ns]
var arr2 = arr

arr2[0].add(2)
arr[0] // [2]

But where classes adopt the NSCopying protocol you can use copy() and mutableCopy() to make the necessary copies to resolve this:

var arr3 = arr.map(){$0.mutableCopy() as! NSMutableArray}
arr3[0].add(1)
arr3[0] // [2, 1]
arr[0] // [2]
arr2[0] // [2]

Where a class does not adopt the NSCopying protocol, you will need to extend or subclass, adding a copy(with:) method:

extension UIView: NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
return UIView(frame: self.frame)
}
}

var aView = UIView(frame:CGRect(x: 0, y: 0, width: 100, height: 100))
var bView = aView.copy() as! UIView

aView.tag = 5
bView.tag // 0
aView.tag // 5

Note: The code example of copying a UIView is incredibly simplistic and assumes that a "copy" is simply a view the same size, whereas a copy in real use would be something more complex, most likely containing subviews, etc.

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.

Copy array of NSManagedObject objects in Swift 4

To translate that Objective-C code into Swift you do:

var questionsCopy = NSArray(array: origQuestions, copyItems:true) as! [Questions]

But since you declared origQuestions as optional, it needs to be:

var questionsCopy = origQuestions != nil ? NSArray(array: origQuestions!, copyItems:true) as? [Questions] : nil

Whether that fully works (Objective-C or Swift) with NSManagedObject or not is another question. See How can I duplicate, or copy a Core Data Managed Object? and its Swift answers for code that specifically covers doing deep copies of NSManagedObject instances.

Make a shallow copy of collection classes (Array, Dictionary) in swift, not a deep copy.

It happens because name is a Swift String instance. String is struct, thus it's a value type. That is because it's always copied by value. arrayObject1 as [AnyObject] is a conversion to swift Array (a struct too) with String objects within.

So it's not trivial to get a shallow copy using such structures like Array and String in Swift.

I can only come up with an idea of boxing struct instances into a class wrapper:

class StringBox : CustomStringConvertible, NSCopying {
var string: String

init(str: String) {
string = str
}

var description: String {
return "\(string)"
}

@objc func copyWithZone(zone: NSZone) -> AnyObject {
return StringBox(str: string)
}
}

func createAnArrayUsingArrayCopyItems(){
let name = StringBox(str: "albort")
let arrayObject1 = NSArray.init(objects: name, 15)
let arrayObject2 = NSMutableArray.init(array: arrayObject1 as [AnyObject], copyItems: false)
(arrayObject2[0] as! StringBox).string = "john"
print(arrayObject1[0])
print(arrayObject2[0])
}

createAnArrayUsingArrayCopyItems()

It gives me a following output:

john
john


Related Topics



Leave a reply



Submit