Глубокая копия против мелкой копии – и как вы можете использовать их в Swift

1656594975 glubokaya kopiya protiv melkoj kopii – i kak vy mozhete

от Payal Gupta

1*7s9oXXuSiTw_HDCwc0Mrqw

Копирование объекта всегда являлось важной частью парадигмы кодирования. Будь то Swift, Objective-C, JAVA или любой другой язык, нам всегда нужно будет скопировать объект для использования в разных контекстах.

В этой статье мы подробно обсудим, как копировать разные типы данных в Swift и как они ведут себя в разных обстоятельствах.

Типы значения и ссылки

Все типы данных в Swift разделяются на две категории, а именно типы ценностей и справочные типы.

  • Тип значения – каждый экземпляр хранит уникальную копию своих данных. Типы данных, попадающие в эту категорию, включают: all the basic data types, struct, enum, array, tuples.
  • Тип ссылки — экземпляры совместно используют одну копию данных, а тип обычно определяется как a class.

Наиболее отличительной чертой обоих типов является их копировальное поведение.

Что такое глубокая копия?

Экземпляр, будь то тип значения или тип ссылки, можно скопировать одним из следующих способов:

Глубокая копия Все дублирует

  • При глубокой копии копируется любой объект, на который указывает источник, а копия указывает место назначения. Таким образом, будут созданы два абсолютно отдельных объекта.
  • Коллекции — Глубокая копия коллекции — две коллекции, в которых дублируются все элементы оригинальной коллекции.
  • Менее склонен к гонкам и хорошо работает в многопоточной среде – изменения в одном объекте не будут влиять на другой объект.
  • Типы значений копируются глубоко.

В приведенном выше коде

  • Строка 1: arr1 — массив (тип значения) строк
  • Строка 2: arr1 назначается к arr2. Это создаст глубокую копию arr1 а затем назначить эту копию arr2
  • Строки с 7 по 11: любые изменения, внесенные в arr2 не отображать в arr1 .

Вот что такое глубокая копия – совершенно отдельные экземпляры. Та же концепция работает со всеми типами значений.

В некоторых сценариях, когда тип значения содержит вложенные типы ссылок, глубокая копия выявляет другой тип поведения. Мы увидим это в следующих разделах.

Мелкая копия Как можно меньше копий

  • При неглубокой копии любой объект, на который указывает источник, также указывает место назначения. Таким образом, в памяти будет создан только один объект.
  • Коллекции — Мелкая копия коллекции – это копия структуры коллекции, а не элементов. В неглубокой копии две коллекции разделяют отдельные элементы.
  • Быстрее — копируется только справка.
  • Копирование справочные типы создает неглубокую копию.

В приведенном выше коде

  • Строки с 1 по 8: Address тип класса
  • Строка 10: a1 — экземпляр Address типа
  • Строка 11: a1 назначается к a2. Это создаст неглубокую копию a1 а затем назначить эту копию a2 то есть скопирована только ссылка a2.
  • Строки с 16 по 19: любые изменения, внесенные в a2 непременно отобразится a1 .
1*HCBKXip1e4ACmsDcDcGORA

На иллюстрации выше мы видим, что оба a1 и a2 указывают на тот же адрес памяти.

Глубокое копирование справочных типов

На данный момент мы знаем, что каждый раз, когда мы пытаемся скопировать тип ссылки, копируется только ссылка на объект. Новый объект не создается. Что делать, если мы хотим создать совершенно отдельный объект?

Мы можем создать глубокую копию типа ссылки с помощью copy() метод. Согласно документации,

copy() — Возвращает возвращающий объект copy(with:).

Это удобный метод для классов, использующих NSCopying протокол. Исключение возникает, если нет реализации для copy(with:).

Давайте перестроим Address class мы создали во фрагменте кода 2 для соответствия NSCopying протокол.

В приведенном выше коде

  • Строки с 1 по 14: Address тип класса соответствует NSCopying и орудие copy(with:) метод
  • Строка 16: a1 — экземпляр Address типа
  • Строка 17: a1 назначается к a2 использование copy() метод. Это создаст глубокую копию a1 а затем назначить эту копию a2 то есть будет создан совершенно новый объект.
  • Строки с 22 по 25: любые изменения, внесенные в a2 не отобразится в a1 .
1*KRe3gHvPBmPeclEctU4qA

Как видно из иллюстрации выше, оба a1 и a2 указывать на разные места памяти.

Давайте рассмотрим другой пример. На этот раз мы увидим, как это работает вложенные типы ссылок — тип ссылки, содержащий другой тип ссылки.

В приведенном выше коде

  • Строка 22: глубокая копия p1 назначается к p2 используя copy() метод. Это означает, что какие-либо изменения в одном из них не должны оказывать никакого влияния на другой.
  • Строки с 27 по 28: p2’s name и city значения изменяются. Они не должны отображаться p1.
  • Строка 30: p1’s name как и ожидалось, но да city? Она должна быть “Mumbai” не должно ли быть? Но мы этого не видим. “Bangalore” было только для p2 да? Так точно.?

Глубокая копия…!? Тшляпы от тебя не ждали. Вы сказали, что скопируете все. А сейчас ты ведешь себя так. Почему о, почему..?! Что мне теперь делать? ☠

Не паникуйте. Давайте посмотрим, что здесь должны сказать адреса памяти.

1*6AbWa3I3gC4p-PsFCILOew

Из приведенной выше иллюстрации мы видим это

  • p1 и p2 указывать на разные места памяти, как ожидалось.
  • Но их address переменные все еще указывают на то же место. Это означает, что даже после их глубокого копирования копируются только ссылки, т.е. неглубокая копия конечно.

Пожалуйста, запиши: всякий раз, когда мы копируем ссылочный тип, по умолчанию создается неглубокая копия, пока мы явно не укажем, что ее следует копировать глубоко.

func copy(with zone: NSZone? = nil) -> Any{    let person = Person(self.name, self.address)    return person}

В вышеприведенном методе, который мы реализовали ранее для Person класса, мы создали новый экземпляр, скопировав адрес из self.address . Это скопирует только ссылку на объект адреса. Это причина того, почему оба p1 и p2’s address указать на то же место.

Итак, копируя объект с помощью copy() метод не создаст подлинную глубокую копию объекта.

Чтобы полностью скопировать опорный объект: тип ссылки вместе со всеми вложенными типами ссылок необходимо скопировать с помощью copy() метод.

let person = Person(self.name, self.address.copy() as? Address)

Используя приведенный выше код в func copy(with zone: NSZone? = nil) -> Любой метод позволит всё работать. Вы можете увидеть это из иллюстрации ниже.

1*XdsZvbu6N5jYEDumL3PCUw

True Deep Copy — типы ссылки и значения

Мы уже видели, как можно создать глубокую копию типов ссылок. Конечно, мы можем сделать это со всеми вложенными типами ссылок.

Но как насчет вложенного типа ссылки в типе значения, то есть массива объектов, или переменного типа ссылки в структуре или, возможно, кортежи? Можем ли мы решить это с помощью copy() тоже? Нет, мы не можем. The copy() метод требует внедрения NSCopying протокол, который работает только для NSObject подклассы. Типы значений не поддерживают наследования, поэтому мы не можем использовать copy() с ними.

В строке 2 только структура arr1 глубоко скопировано, но Address объекты внутри него все еще копируются неглубоко. Вы можете увидеть это из карты памяти ниже.

1*P4qAq9o-mqCbhiHx0mfnYA

Элементы у обоих arr1 и arr2 оба указывают на одни и те же места памяти. Это связано с той же причиной – типы ссылок неглубоко копируются по умолчанию.

Сериализация, а затем десериализация объект всегда создает совершенно новый объект. Он действителен как для типов значений, так и для типов ссылок.

Вот некоторые API, которые мы можем использовать для сериализации и десериализации данных:

  1. NSCoding — протокол, позволяющий кодировать и декодировать объект для архивирования и распространения. Это будет работать только с class объекты типа, поскольку он требует наследования от NSObject .
  2. Кодирование — Сделайте типы данных кодированными и декодированными для совместимости с внешними представлениями, такими как JSON. Он будет работать для обоих типов значений. struct, array, tuple, basic data typesа также эталонные типы. class .

Давайте перестроим Address класс немного дальше, чтобы отвечать Codable протокол и удалить все NSCopying код, добавленный ранее во фрагменте кода 3.

В приведенном выше коде строки 11–13 создадут настоящую глубокую копию arr1. Ниже приведена иллюстрация, которая дает ясное представление о местах в памяти.

1*17lE3zZhorgMa6GFLNoOoA

Копировать на Написать

Копирование при записи – это метод оптимизации, который помогает повысить производительность копирования типов значений.

Скажем, мы копируем один String или Int или, возможно, любой другой тип значения — в этом случае мы не столкнемся ни с какими существенными проблемами с производительностью. Но что делать, когда мы копируем массив из тысячи элементов? Это все равно не создаст проблем с производительностью? Что если мы просто скопируем его и не внесем никаких изменений в эту копию? Разве эта дополнительная память, которую мы использовали, не является пустой тратой в этом случае?

Здесь возникает концепция копирования при записи — при копировании каждая ссылка указывает на тот же адрес памяти. Только когда одна из ссылок изменяет основные данные, Swift фактически копирует исходный экземпляр и вносит изменения.

То есть будь то глубокая копия или мелкая копия, новая копия не будет создана, пока мы не внесем изменения в один из объектов.

В приведенном выше коде

  • Строка 2: глубокая копия arr1 назначается к arr2
  • Строки 4 и 5: arr1 и arr2 все еще указывают на тот же адрес памяти
  • Строка 7: изменения внесены в arr2
  • Строки 9 и 10: arr1 и arr2 теперь указывает на разные места памяти

Теперь вы знаете больше о глубоких и неглубоких копиях и как они ведут себя в разных сценариях с разными типами данных. Вы можете попробовать их на своем наборе примеров и увидеть, какие результаты вы получите.

Дальнейшее чтение

Не забудьте прочитать другие мои статьи:

  1. Все о Codable в Swift 4
  2. Все, что вы всегда хотели знать об уведомлениях в iOS
  3. Раскрасьте его с помощью GRADIENTS — iOS
  4. Кодирование для iOS 11: как перетаскивать в коллекции и таблицы
  5. Все, что вам нужно знать о Today Extensions (Виджет) в iOS 10
  6. Легкий выбор UICollectionViewCell..!!

Не стесняйтесь оставлять комментарии, если у вас есть вопросы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *