具有引用类型项的 Swift 数组的 CoW(写时复制)异常

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

我的理解:

Swift 中的数组是值类型。 Swift 中的数组和其他集合具有 CoW(写时复制)机制,因此当数组作为参数传递给函数或简单地分配给另一个变量时,Swift 实际上不会创建数组的另一个副本而是简单地将引用传递给同一个数组。在尝试 write/modify 数组时,swift 将创建一个新的数组副本(假设原始数组引用仍然被强烈保留)并且将对数组的新副本执行写操作。

背景:

在这个问题中,我试图存储 class 个实例(数组中的引用类型)

class TestClass {
    var name: String = "abcd"
    init(name: String) {
        self.name = name
    }
}

我正在创建 TestClass 的局部变量 a(数组)并将其作为参数传递给 someFunc

override func viewDidLoad() {
    super.viewDidLoad()
    var a = [TestClass(name: "abcd"), TestClass(name: "efgh"), TestClass(name: "ijkl")]
    debugPrint(UnsafePointer(&a))
    self.someFunc(array: a)
}

someFunc中我将参数分配给另一个变量anotherArray并对anotherArray执行append操作。正如预期的那样,Array 的 CoW 启动并创建了一个新的 Array 副本,因此 arrayanotherArray 的内存地址不同。

func someFunc(array: [TestClass]) {
    var anotherArray = array
    anotherArray.append(TestClass(name: "mnop"))

    debugPrint(UnsafePointer(&array))
    debugPrint(UnsafePointer(&anotherArray))
}

不出所料,复制一个值类型时,所有内部引用类型也将是recreated/copied,证明

  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))
    }
}

显然数组的内存地址不同(array !=== anotherArray)并且arrayanotherArray中所有项目的内存地址也不同(array[i] !=== anotherArray[i]

问题:

    func someFunc(array: [TestClass]) {
        var anotherArray = array
        anotherArray.append(TestClass(name: "mnop"))
        anotherArray[0].name = "Sandeep"
        debugPrint(array[0].name)
    }

清楚array 和anotherArray 是两个不同的副本,并且每个数组中的引用类型也完全不同,如果我将anotherArray[0].name 的值更改为"Sandeep",人们会期望,array[0].name 应该仍然是 "abcd" 但它 returns "Sandeep"

这是为什么?我在这里错过了什么吗?它与 Array 的特殊访问器 mutableAddressWithPinnedNativeOwner 有什么关系吗?

数组的特殊访问器mutableAddressWithPinnedNativeOwner

如果我理解正确,而不是像 Dictionary 那样简单地提取特定索引处的值,复制它,修改它并替换原始值,mutableAddressWithPinnedNativeOwner 只需访问特定索引处值的物理内存并修改它。 但是当整个数组本身被修改时,这应该不会有什么不同:|在这里很困惑

完成运行代码:

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)
    }
}

看起来 UnsafePointer(&value) returns 是错误的值(可能是数组的头部或类似的东西)。我稍微更改了 someFunc

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)
}

输出如下:

0x0000600003f29360
0x0000600003f29380
0x0000600003f29400

0x0000600003f29360
0x0000600003f29380
0x0000600003f29400
0x0000600003f29340

如您所见,两个数组包含相同的对象,这是预期的行为。数组存储对 TestClass 个对象(不是值)的引用,并在 CoW 期间复制这些引用,但对象保持不变。