Implementing Copy() in Swift

Swift making copies of passed class instances

Wrote the following (with the help of a friend) in playground:

protocol Copyable {
func copyOfValues() -> AnyObject
}

extension Copyable where Self: NSObject {
func copyOfValues() -> AnyObject{
var copyOfOriginal = Self()
let mirror = Mirror(reflecting: self)
for (label, value) in mirror.children {
if let label = label {
copyOfOriginal.setValue(value, forKey: label)
}
}

return copyOfOriginal
}
}

class Test: NSObject, Copyable {
var a = 1
var b = 2
}

var test = Test()
var copy = test.copyOfValues() as! Test

print(dump(test))
print(dump(copy))
copy.a = 10
print(dump(test))
print(dump(copy))

This is a nice and simple function so that I can obtain copy of my class instances in swift. In swift, since they are a reference type (and I am not sure if you can dereference it or whatnot) you would basically have to write a custom copy function for your objects every time. Well, Now I wrote this, so as long as you are using a subclass of NSObject and use this protocol, you'll be fine.

This has worked exactly as I need in my code

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 command - deep or shallow copy?

The copy performs a shallow copy (a copy of the collection, but the objects in the array are not copied). E.g.

let obj1 = Object(value: 1)
let obj2 = Object(value: 2)
let obj3 = Object(value: 3)

let originalArray = [obj1, obj2, obj3] as NSArray
let copyArray = originalArray.copy() as! NSArray

print(String(format: "original address: %p", originalArray))

for obj in originalArray {
print(String(format: " %p", obj as! Object))
}

print(String(format: "copy address: %p", copyArray))

for obj in copyArray {
print(String(format: " %p", obj as! Object))
}

Note, I use an object other than NSNumber and NSString, as those have optimizations that may make looking at addresses misleading.

The member objects of these two arrays point to the same objects (i.e. a shallow copy).

original address: 0x618000044920
0x6180000277e0
0x618000027f00
0x618000027ea0
copy address: 0x618000044920
0x6180000277e0
0x618000027f00
0x618000027ea0

In fact, as you can see, because it's an immutable NSArray, it appears to optimize this where the copy is actually returning the same array instance. If you use mutable arrays, NSMutableArray, you'll see two unique arrays returned, as you'd expect, but they will still point to the same collection of objects.

But, if you use NSArray(array:copyItems:) with true for copyItems, you will get two unique arrays with unique copies of each member object:

let copyArray = NSArray(array: originalArray as! [Any], copyItems: true)

That yields a deep copy (two unique arrays where the individual member objects are copied and they, too, have unique addresses):

original address: 0x618000059e60
0x618000027ae0
0x618000028340
0x618000028280
copy address: 0x61800005ba20
0x618000028800
0x618000028aa0
0x618000028400

See the documentation for init(array:copyItems:) which says:

The copy(with:​) method performs a shallow copy. If you have a collection of arbitrary depth, passing true for the flag parameter [of init(array:copyItems:)] will perform an immutable copy of the first level below the surface. If you pass false the mutability of the first level is unaffected. In either case, the mutability of all deeper levels is unaffected.


Personally, though, if writing Swift, I'd generally use Array and Dictionary value types rather than the old NSArray and NSDictionary types.



Related Topics



Leave a reply



Submit