简体   繁体   中英

Anomaly in CoW (Copy on Write) of Swift Array with Reference type items

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM