My understanding:
Arrays in Swift are value types. Arrays and other collections in Swift has CoW (Copy on Write) mechanism so when an array is passed as an argument to a function or simply assigned to another variable, Swift will not actually create another copy of array rather simply passes the reference to same array. On attempting to write/modify the array, swift will create a new copy of Array (Assuming that original array reference is still strongly held) and write operation will be performed on new copy of the array.
Background:
In this problem, I am trying to store class instances (reference types in Array)
class TestClass {
var name: String = "abcd"
init(name: String) {
self.name = name
}
}
I am creating a local variable a (array) of TestClass
and passing it as an argument to someFunc
override func viewDidLoad() {
super.viewDidLoad()
var a = [TestClass(name: "abcd"), TestClass(name: "efgh"), TestClass(name: "ijkl")]
debugPrint(UnsafePointer(&a))
self.someFunc(array: a)
}
In someFunc
I assign the argument to another variable anotherArray
and perform append
operation on anotherArray
. As Expected CoW of Array kicks in and creates a new copy of Array so memory addresses of array
and anotherArray
are different.
func someFunc(array: [TestClass]) {
var anotherArray = array
anotherArray.append(TestClass(name: "mnop"))
debugPrint(UnsafePointer(&array))
debugPrint(UnsafePointer(&anotherArray))
}
As expected, when a value type is copied, all the inner reference types will also be recreated/copied, to prove that
func someFunc(array: [TestClass]) {
var anotherArray = array
anotherArray.append(TestClass(name: "mnop"))
for var value in array {
debugPrint(UnsafePointer(&value))
}
for var value in anotherArray {
debugPrint(UnsafePointer(&value))
}
}
Clearly memory addresses of the arrays are different (array !=== anotherArray
) and also memory addresses of all the items inside array
and anotherArray
are also different (array[i] !=== anotherArray[i]
)
Issue:
func someFunc(array: [TestClass]) {
var anotherArray = array
anotherArray.append(TestClass(name: "mnop"))
anotherArray[0].name = "Sandeep"
debugPrint(array[0].name)
}
With the clear understanding that array and anotherArray are two different copies and also reference types inside each array are completely different, one would expect if I change the value of anotherArray[0].name
to "Sandeep", array[0].name
should still be "abcd" but it returns "Sandeep"
Why is that? Am I missing something here? Has it got anything to do with Array's Special Accessor mutableAddressWithPinnedNativeOwner
?
Array's Special Accessor mutableAddressWithPinnedNativeOwner
If I understand it correctly, rather than simply pulling out the value at specific index, copying it, modifying it and replacing the original value like in case of Dictionary, mutableAddressWithPinnedNativeOwner
simply access the physical memory of the value at specific index and modifies it.
But that shouldn't make a difference when entire array itself is modified :| Am confused here
Complete running code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var a = [TestClass(name: "abcd"), TestClass(name: "efgh"), TestClass(name: "ijkl")]
debugPrint(UnsafePointer(&a))
self.someFunc(array: a)
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func someFunc(array: [TestClass]) {
var anotherArray = array
anotherArray.append(TestClass(name: "mnop"))
for var value in array {
debugPrint(UnsafePointer(&value))
}
for var value in anotherArray {
debugPrint(UnsafePointer(&value))
}
anotherArray[0].name = "Sandeep"
debugPrint(array[0].name)
}
}
Looks like UnsafePointer(&value)
returns the wrong value (maybe it is the head of the array or something like this). I changed the someFunc
a little bit.
func someFunc(array: [TestClass]) {
var anotherArray = array
anotherArray.append(TestClass(name: "mnop"))
for var value in array {
debugPrint(Unmanaged.passUnretained(value).toOpaque())
}
for var value in anotherArray {
debugPrint(Unmanaged.passUnretained(value).toOpaque())
}
anotherArray[0].name = "Sandeep"
debugPrint(array[0].name)
}
And the output is the following:
0x0000600003f29360
0x0000600003f29380
0x0000600003f29400
0x0000600003f29360
0x0000600003f29380
0x0000600003f29400
0x0000600003f29340
As you can see, both arrays contain the same objects, and this is the expected behavior. Array stores references to TestClass
objects (not values) and copies these references during CoW, but the objects remain the same.