Найти в Дзене
Swift Explorer

Swift: Value vs Reference типы, когда какой использовать?

В Swift есть две категории типов: типы значений и типы ссылок. Экземпляр типа значения хранит уникальную копию своих данных, например, структуру или перечисление. Тип ссылки, имеет одну копию своих данных, и тип обычно является классом.
Мы также обсудим такие типы, как кортежи, замыкания и функции, как они работают, как их использовать и что происходит при смешивании типов. Для того чтобы погрузиться в тему скачайте с репозитория следующий playground проект Value типы (типы значения) Экземпляр типа значения является независимым экземпляром и хранит свои данные в собственной памяти. Существует несколько различных типов значений: структура, перечисление и кортеж. (struct, enum, и tuple) Struct Давайте поэкспериментируем со структурами и докажем, что они являются типами значений: Откройте playground проект на странице - 1 - Struct В приведенном выше коде вы сделали: Даже не смотря на то что plane это копия boeing, оба инстанса остаются независимыми друг от друга со своими уникальными ко
Оглавление

В Swift есть две категории типов: типы значений и типы ссылок. Экземпляр типа значения хранит уникальную копию своих данных, например, структуру или перечисление. Тип ссылки, имеет одну копию своих данных, и тип обычно является классом.

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

  • Value типы
  • Reference типы
  • Mutability (Изменчивость)
  • Mixed Types (Смешанные типы)
  • Pointers (Указатели)
  • Итоги

Для того чтобы погрузиться в тему скачайте с репозитория следующий playground проект

Value типы (типы значения)

Экземпляр типа значения является независимым экземпляром и хранит свои данные в собственной памяти. Существует несколько различных типов значений: структура, перечисление и кортеж. (struct, enum, и tuple)

Struct

Давайте поэкспериментируем со структурами и докажем, что они являются типами значений:

Откройте playground проект на странице - 1 - Struct

В приведенном выше коде вы сделали:

  1. Создали структуру AirPlane со свойствами brand и model.
  2. Создали новый инстанс AirPlane именовый boeing
  3. Создали копию инстанса boeing и назвали его plane
  4. Поменяли boeing.model на "А320"
  5. Отправили в консоль оба инстанса.Первый распечатает AirPlane(brand: "Boeing", model: "B-737"), второй распечатает AirPlane(brand: "Boeing", model: "A320").

Даже не смотря на то что plane это копия boeing, оба инстанса остаются независимыми друг от друга со своими уникальными копиями данных.

Таким образом мы понимаем, что struct действительно относится к типу значений, а не ссылочному.

Enum

Чтобы убедиться, что перечисления являются типами значений, давайте также проведем эксперимент

Откройте playground проект на странице - 2 - Enum

В приведенном выше коде вы сделали:

  1. Создали перечисление Planet и поместили в него earth и venus
  2. Создали новый инстанс Planet и назвали его earth, присвоив ему кейс earth.
  3. Создали копию earth с именем venus
  4. Поменяли инстанс earth на venus
  5. Отправили в консоль оба инстанса, даже не смотря на то что venus изначально был копией earth и в четвертом пункте мы заменили earth на venus оба инстанса остались разными, а не одинаковыми как если бы это был ссылочный тип данных

Tuple

Последний тип значения, который мы рассмотрим, - это кортеж. Тип кортежа - это разделенный запятыми список из нуля или более типов, заключенный в скобки. Вы можете получить доступ к его значениям с помощью нотации точки (.), за которой следует индекс значения.

Вы также можете назвать элементы в кортеже и использовать имена для доступа к различным значениям.

И снова эксперимент

Откройте playground проект на странице - 3 - Tuple

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

  1. Создали кортеж batman с двумя строками - Thomas и Wayne
  2. Создали копию инстанса batman с именем parent.
  3. Поменяли batman.0 index на Bruce.
  4. Отправили в консоль оба инстанса и вновь увидели, что они разные и независимые друг от друга

Теперь вы можете быть уверены, что структуры, перечисления и кортежи являются value типами!

Когда использовать типы значений

Используйте value типы когда сравнение инстансов имеет смысл через оператор ==

== сравнивает является ли каждый экземпляр одинаковым по отношению друг к другу


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

Используйте тип значения, если вы хотите, чтобы копии имели независимое состояние, и данные будут использоваться в коде в нескольких потоках.

В Swift Array, String и Dictionary - это все типы значений.

Типы ссылок

В Swift экземпляры ссылочного типа используют одну копию своих данных, так что каждый новый экземпляр будет указывать на один и тот же адрес в памяти. Типичным примером является класс, функция или клоужер (closure).

Откройте playground проект на странице - 4 - Class


Чтобы разобраться в этом давайте добавим в playground функцию с помощью которой мы будем проверять адрес объекта:

func address<T: AnyObject>(of object: T) -> Int {

return unsafeBitCast(object, to: Int.self)

}

Эта функция выводит адрес объекта, что поможет вам проверить, ссылаетесь ли вы на один и тот же экземпляр или нет.

Класс

Первый ссылочный тип, на который вы посмотрите, - это класс.

IВ приведенном коде вы сделали:

  1. Создали новый класс с именем Dog, который соответствует CustomStringConvertible для печати пользовательских описаний объекта.
  2. Определили пользовательское описание объекта.
  3. Создали новую функцию инициализации. Это необходимо, потому что, в отличие от структуры, класс не создает автоматически функцию инициализации на основе переменных объекта.
  4. Создали экземпляр doberman - Dog.
  5. Создали копию doberman по имени .
  6. Изменили doberman.age на 2.
  7. Изменили chihuahua.weight на 10.
  8. Распечатали описание двух разных экземпляров. Первый принт, печатает Возраст 2 - Вес 10, а второй печатает то же самое; Возраст 2 - Вес 10. Это потому, что вы на самом деле ссылаетесь на один и тот же объект.
  9. Распечатали адрес двух разных экземпляров. С помощью этих принтов вы будете уверены, что ссылаетесь на один и тот же адрес. Вы увидите, что обе инструкции print печатают одно и то же значение.

Теперь вы можете быть уверены, что класс является ссылочным типом.

Функции и клоужеры

Замыкание используется для обозначения функции вместе с переменными из области ее действия, которые она инкапсулирует. Функции - это, по сути, замыкания, которые хранят ссылки на переменные в их контексте.

Взгляните на код ниже:

Откройте playground проект на странице - 5 - functions & closures

Они оба делают одно и то же.

Вы можете найти более подробную информацию о клоужерах в
документации Swift.

Когда использовать типы ссылок

Используйте ссылочный тип когда сравнение идентичности объектов через оператор тождественности === имеет смысл

=== проверяет являются ли сравниваемые объекты идентичными, а именно делят ли они один и тот же адрес в памяти

Они также полезны, когда вы хотите создать объект имеющий общее, изменяемое состояние.

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

Изменчивость (Mutability)

Изменчивость типов значений позволяет специально выбирать, какие переменные могут быть изменены или нет.

Откройте playground проект на странице - 6 - Mutability

В этом коде вы сделали:

  1. создали Bike struct.
  2. создали let и var свойства для него.
  3. создали константу Bike. Даже не смотря на то что km внутри структуры указана как переменная вы не сможете ее менять потому как сам инстанс bike указан как let.

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

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

Следующий код ниже покажет нам работу с классом MotorBike:

В приведенном коде вы сделали:

  1. Создали класс Motorbike.
  2. Создали radius как let и km как var.
  3. Добавили 1 к motorbike.km переменной.
  4. Отправили в консоль переменную motorbike.km.

Вы можете установить переменную km, потому что мотоцикл - это класс, и мы ссылаемся на его адрес памяти.

Ключевое слово Nonmutating

Существует также ключевое слово nonmutating, которое может указать, что константа может быть установлена без изменения содержащего экземпляра, но вместо этого имеет глобальные побочные эффекты.

Откройте playground проект на странице - 7 - NonMutating

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

  1.  Импортировали Foundation потому как без него мы бы не смогли работать с UserDefaults.
  2. Создали структуру Cat.
  3. Создали переменную name.
  4. Обозначили что будет когда мы будем запрашивать name кота - делая запрос в UserDefaults
  5. Обозначили nonmutating set поведение чтобы установить новое имя в UserDefaults если такое имя есть, в ином случае удалить сведения об имени.
  6. Создали новый инстанс Cat, с именем cat, и установили его имя на "Sam".
  7. Создали новый инстанс Cat, с именем tiger, и установили его имя на "Olly".

У обеих кошек теперь имя "Олли", потому что они используют UserDefaults, чтобы получить свое имя. При этом обратите внимание мы работаем со структурой, но даже если обе они константны - вы можете установить их свойство name без каких-либо предупреждений или ошибок. В этом нам помогло ключевое слово nonmutating

Смешанные типы

Что происходит, когда вы смешиваете типы значений со ссылочными типами и наоборот?

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

Смешивание типов значений в ссылочных типах

Давайте начнем со смешивания типов значений в ссылочных типах, добавив структуру внутри класса и посмотрим, что произойдет.

Допустим, у вас есть производитель, который производит какое-то устройство. Вы создадите класс Device и struct Manufacturer.

Откройте playground проект на странице - 8 - Mixing Value Types in Reference Types

В коде вы сделали:

  1. Создали структуру Manufacturer со свойством name.
  2. Создали класс Device со свойствами name и manufacturer.
  3. Установили тип свойства manufacturer как структуру Manufacturer.
  4. Создали manufacturer с именем "Apple".
  5. Создали два девайса, "iPhone" и "iPad".
  6. Переназначили manufacturer для Ipad на "Google".
  7. Отправили имена manufacturer для обоих девайсов в консоль.

Вы увидите, что у двух устройств теперь разные производители. У iPhone Apple в качестве производителя, а у iPad Google в качестве производителя.

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

Можете проверить как изменится поведение если заменить Manufacturer на класс, ожидаемо изменение которое мы отметили в //6 заменит производителя в обоих девайсах.

Смешивание типов ссылок в типах значений

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

На этот раз у вас будет два самолета и один двигатель. Вы определите структуру самолета и класс двигателя. Конечно, у самолета есть двигатель.

Откройте playground проект на странице - 9 - Mixing Reference Types in Value Types

В коде вы сделали:

  1. Создали класс Engine, со свойством type.
  2. Создали структуру Airplane, со свойством engine типа Engine.
  3. Создали двигатель - Jet.
  4. Создали два самолета с одним и тем же экземпляром Engine.
  5. Заменили двигатель littleAirplane на "Rocket".
  6. Отправили в консоль два объекта самолета.

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

Pointers - Указатели

В Swift вы можете ссылаться на экземпляр, используя ключевое слово inout в сигнатурах функций. Использование inout означает, что изменение локальной переменной также изменит переданные параметры. Без него переданные параметры останутся прежними так как является не мутабельными.

Откройте playground проект на странице - 10 - Pointers

В коде вы сделали:

  1. Создали функцию addKm которая принимает inout параметр Bike.
  2. Создали новый экземпляр Bike под названием awesomeBike.
  3. Вызвали функцию addKm с оператором &. Оператор & указывает функции передать адрес переменной вместо ее копии.
  4. Передали в консоль экземпляр awesomeBike.

Вы увидите, что распечатав переменную awesomeBike, ее км составит 30, потому что вы не создаете ее копию. Вместо этого вы используете один и тот же экземпляр.

Попробуйте убрать из функции inout и & оператор из вызова чтобы посмотреть какое поведение покажет компилятор.

Типы ссылок работают также, но вы можете передать их с inout или без, и результат будет таким же.

Откройте playground проект на странице - 10 - Pointers2

Как и раньше, вы создали новый экземпляр мотоцикла под названием motorbike и передали его функции addKm. Функция принт на 23 строчке напечатает 40 км.

Теперь попробуйте убрать из 15 строки inout и & с 21 строки

Результат будет точно таким же!

Итоги

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

Еще раз напоминаю что для изучения материала вы можете скачать проект здесь.

Одним из больших преимуществ использования типов значений Swift по умолчанию, таких как Array, является функция копирования при записи (copy on write). Это может стать вашим следующим шагом в изучении различий между типами значений и типами ссылок!

Также можно прочитать статью Apple по вопросу value vs reference

Если вы дочитали статью до конца:

  1. поставьте лайк
  2. подпишитесь на канал

Это мотивирует писать новые статьи, а вы в свою очередь не упустите обновления!

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