ПАМЯТЬ
Для чего используется оперативная память?
Когда программа выполняется в операционный системе компьютера, она нуждается в доступе к оперативной памяти (RAM) для того, чтобы:

  • загружать свой собственный байт-код для выполнения;
  • хранить значения переменных и структуры данных, которые используются в процессе работы;
  • загружать внешние модули, которые необходимы программе для выполнения задач

Помимо места, используемого для загрузки своего собственного байт-кода, программа использует при работе две области в оперативной памяти — стек (stack) и кучу (heap).
ПАМЯТЬ
СЕГМЕНТЫ ПАМЯТИ
Память - это просто длинный список байтов. Байты расположены упорядоченно, каждый байт имеет свой адрес. Диапазон дискретных адресов известен как адресное пространство.

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

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

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

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

Затраты на выделение и освобождение памяти в куче намного больше, чем на выделение памяти в стэке (https://developer.apple.com/videos/play/wwdc2018/416/)

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

Swift value types are allocated on the stack. Reference types are allocated on the heap.
ПАМЯТЬ
Stack
Stack — Переменные, выделенные в стеке, хранятся непосредственно в памяти, и доступ к этой памяти очень быстрый, и ее выделение определяется при компиляции программы.

  • Стек используется для статичного выделения памяти. Он организован по принципу «последним пришёл — первым вышел» (LIFO). Можно представить стек как стопку книг — разрешено взаимодействовать только с самой верхней книгой: прочитать её или положить на неё новую.

  • Стек позволяет очень быстро выполнять операции с данными — все манипуляции производятся с «верхней книгой в стопке». Книга добавляется в самый верх, если нужно сохранить данные, либо берётся сверху, если данные требуется прочитать;

  • Существует ограничение в том, что данные, которые предполагается хранить в стеке, обязаны быть конечными и статичными — их размер должен быть известен ещё на этапе компиляции;

  • Каждый поток многопоточного приложения имеет доступ к своему собственному стеку;

  • Когда функция вызывается, все локальные экземпляры этой функции будут помещены в текущий стек. И как только функция вернется, все экземпляры будут удалены из стека.

  • Если размер вашего value type может быть определен во время компиляции или если ваш value type не содержит рекурсию на себя или не находится в ссылочном типе, тогда потребуется выделение стека.
ПАМЯТЬ
Heap
Куча используется для динамического выделения памяти, однако, в отличие от стека, данные в куче первым делом требуется найти с помощью «оглавления». Можно представить, что куча это такая большая многоуровневая библиотека, в которой, следуя определённым инструкциям, можно найти необходимую книгу.

  • Операции на куче производятся несколько медленнее, чем на стеке, так как требуют дополнительного этапа для поиска данных;

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

  • Куча общая для всех потоков приложения;

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

ПАМЯТЬ
Автоматический подсчёт ссылок (ARC)
Данный подход весьма похож на сборку мусора с подсчётом ссылок, однако, вместо запуска процесса подсчёта в определённые интервалы времени, инструкции выделения и освобождения памяти вставляются на этапе компиляции прямо в байт-код. Когда же счётчик ссылок достигает нуля, память освобождается как часть нормального потока выполнения программы.

Автоматический подсчёт ссылок всё так же не позволяет обрабатывать циклические ссылки и требует от разработчика использования специальных ключевых слов для дополнительной обработки таких ситуаций. ARC является одной из особенностей транслятора Clang, поэтому присутствует в языках Objective-C и Swift.

ПАМЯТЬ
Концепция Ownership
У Swift уже есть система владения, но она «под прикрытием»: это деталь реализации, на которую программисты не могут повлиять

О ней можно долго говорить, а лучше ознакомиться здесь, но вкратце основные идеи этой концепции:

  • Каждое значение в памяти должно иметь только одну переменную-владельца

  • Когда владелец уходит из области выполнения, память сразу же освобождается. (Можно сказать, что это примерно как подсчёт ссылок на этапе компиляции).

  • Каждая часть кода несет ответственность за то, чтобы в конечном итоге вызвать уничтожение объекта
ПАМЯТЬ
Value/Reference Types
Value и Reference Types — это основные концепции Swift. В Swift есть три способа объявления типа: классы, структуры и перечисления. Их можно разделить на типы значений (структуры и перечисления) и ссылочные типы (классы). То, как они хранятся в памяти, определяет разницу между ними:

  • Value Type — каждая переменная типа значения имеет свою собственную копию данных, и операции с одной не влияют на другую. За него отвечает стэк.

  • Reference Type — у нас есть ссылка, указывающая на это место в памяти. Переменные ссылочного типа могут указывать на одни и те же данные; следовательно, операции с одной переменной могут повлиять на данные, указанные другой переменной. За него отвечает куча.
Основные замеры по которым делается сравнение структур и классов это:

  • стоимость копирования
  • стоимость allocation и deallocation
  • стоимость подсчета ссылок

ПАМЯТЬ
Назначение в Stack ссылочных типов
Компилятор Swift может продвигать ссылочные типы для размещения в стеке, когда их размер фиксирован или время жизни может быть предсказано. Эта оптимизация происходит на этапе генерации SIL.

Swift Intermediate Language (SIL) - это промежуточный язык высокого уровня, ориентированный на Swift, подходящий для дальнейшего анализа и оптимизации кода Swift.
  1. При соблюдении протокола. Помимо затрат на выделение ресурсов, возникают дополнительные накладные расходы, когда тип значения хранится в экзистенциальном контейнере и превышает длину 3 машинных слов.

    Экзистенциальный контейнер - это общий контейнер для значения неизвестного типа среды выполнения. Value Types небольших размеров могут быть встроены в экзистенциальный контейнер. Более крупные размещаются в куче, и ссылка на них хранится в экзистенциальном буфере контейнера. Время жизни таких значений управляется Value Witness Table. Это вводит накладные расходы на подсчет ссылок и несколько уровней косвенного обращения при вызове методов протокола

  2. При смешивании value и reference типов. Обычно ссылка на класс хранится в структуре, а структура является полем класса:
// Class inside a struct
class A {}
struct B { 
  let a = A() 
}

// Struct inside a class
struct C {}
class D {
    let c = C()
}
3. Generic с value типом
struct Bas<T> {
    var x: T

    init(xx: T) {
        x = xx
    }
}
4. Escaping closure captures.
В сlosure все локальные переменные фиксируются по ссылке. Некоторые из них все еще могут быть переведены в стек, как описано в CapturePromotion.

SIL указывает, что определение функции закрывается над символической ячейкой памяти. Эта инструкция является переменной.

5.Inout аргумент
Аргументы @inout передаются в точку входа по адресу. Вызываемый объект не получает права собственности на указанную память. Указанная память должна быть инициализирована при входе в функцию и выходе из нее. Если аргумент @inout относится к хрупкой физической переменной (Unowned Unsafe), то аргументом является адрес этой переменной. Если аргумент @inout относится к логическому свойству, тогда аргумент является адресом буфера обратной записи, принадлежащего вызывающей стороне.
func inout(_ x: inout Int) {
  x = 1
}
ПАМЯТЬ
Цена копирования
Как говорили выше, большинство value types размещаются в стеке, и их копирование занимает постоянное время. На скорость влияет то, что примитивные типы, такие как целые числа и числа с плавающей запятой, хранятся в регистрах ЦП, и при их копировании нет необходимости обращаться к оперативной памяти. Большинство расширяемых типов Swift, таких как строки, массивы, наборы и словари, копируются при записи (copy-on-write). Это означает, что копирование происходит только в момент мутации.

Поскольку ссылочные типы не хранят свои данные напрямую, мы несем затраты на подсчет ссылок только при их копировании.

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