Аномалия в CoW (копирование при записи) массива Swift с элементами ссылочного типа

Мое понимание:

Массивы в Swift являются типами значений. Массивы и другие коллекции в Swift имеют механизм CoW (Copy on Write), поэтому, когда массив передается в качестве аргумента функции или просто назначается другой переменной, Swift фактически не создает другую копию массива, а просто передает ссылку на тот же массив. . При попытке записи/изменения массива Swift создаст новую копию массива (при условии, что исходная ссылка на массив все еще сильно удерживается), и операция записи будет выполняться для новой копии массива.

Предыстория:

В этой проблеме я пытаюсь сохранить экземпляры классов (ссылочные типы в массиве)

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

Я создаю локальную переменную a (массив) из TestClass и передаю ее в качестве аргумента 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 и выполняю append операцию над anotherArray. Поскольку ожидается, что CoW массива срабатывает и создает новую копию массива, поэтому адреса памяти array и anotherArray различны.

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

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

введите здесь описание изображения

Как и ожидалось, когда тип значения копируется, все внутренние ссылочные типы также будут воссозданы/скопированы, чтобы доказать, что

  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), а также адреса памяти всех элементов внутри array и anotherArray тоже разные (array[i] !=== anotherArray[i])

Проблема:

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

При четком понимании того, что массив и другой массив являются двумя разными копиями, а также ссылочные типы внутри каждого массива совершенно разные, можно было бы ожидать, что если я изменю значение anotherArray[0].name на «Sandeep», array[0].name все еще должно быть «abcd», но он возвращает «Sandeep "

введите здесь описание изображения

Почему это? Я что-то упустил здесь? Это как-то связано со специальным средством доступа Array mutableAddressWithPinnedNativeOwner?

Специальный метод доступа массива mutableAddressWithPinnedNativeOwner

Если я правильно понимаю, вместо того, чтобы просто извлекать значение по определенному индексу, копировать его, изменять и заменять исходное значение, как в случае Словарь, 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)
    }
}

person Sandeep Bhandari    schedule 03.05.2020    source источник
comment
Это настоящий код? Адреса обоих массивов действительно разные, но адреса внутри массивов всегда одинаковы соответственно.   -  person vadian    schedule 03.05.2020
comment
@vadian: Да, это настоящий код. Адреса массивов различны, также если вы посмотрите на адреса элементов массива и другого массива, они будут 0x00007ffee4efce38 и 0x00007ffee4efce40 соответственно.   -  person Sandeep Bhandari    schedule 03.05.2020
comment
@vadian: выложил полный код в последнем обновлении   -  person Sandeep Bhandari    schedule 03.05.2020
comment
На втором скриншоте я вижу 3 одинаковых адреса, заканчивающихся на 0df8 и 4 на 0e00   -  person vadian    schedule 03.05.2020
comment
@vadian: Вот что меня тоже смутило :) Если вы видите, что в массиве было 3 элемента, поскольку адреса всех трех элементов были одинаковыми, я подумал, что может быть некоторая оптимизация в Swift, поэтому я намеренно установил разные имена для каждого экземпляра, надеясь, что адрес будет быть разными, но они были одинаковыми :| последние 4 после добавления элемента в массив, поэтому теперь он показывает один и тот же адрес для всех 4 элементов: |   -  person Sandeep Bhandari    schedule 03.05.2020
comment
с использованием Xcode 11.3.1 и быстрой версии Swift5   -  person Sandeep Bhandari    schedule 03.05.2020


Ответы (1)


Похоже, UnsafePointer(&value) возвращает неправильное значение (возможно, это заголовок массива или что-то в этом роде). Я немного изменил 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, но объекты остаются прежними.

person Alexander Gaidukov    schedule 03.05.2020
comment
Удивительно :) Это означает, что когда тип значения копируется, его ссылочные типы копируются только поверхностно (указывая на тот же экземпляр), а не воссоздаются (глубокое копирование), не так ли? Является ли такое поведение ссылочных типов поверхностного копирования ограниченным массивом или оно применимо ко всем типам значений? - person Sandeep Bhandari; 03.05.2020
comment
Он применим для всех значений. Дело в том, что система не может создать копию объекта ссылочного типа, потому что для таких объектов нет реализации метода copy по умолчанию. И очевидно, почему это так. Если несколько объектов ссылочного типа создают цикл сохранения, то у нас будет бесконечный цикл, если система попытается создать копию любого из этих объектов. - person Alexander Gaidukov; 04.05.2020