Управление памятью в Swift
ГЛАВА I
Для чего используется оперативная память?
Когда программа выполняется в операционный системе компьютера, она нуждается в доступе к оперативной памяти (RAM) для того, чтобы:

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

Помимо места, используемого для загрузки своего собственного байт-кода, программа использует при работе две области в оперативной памяти — стек (stack) и кучу (heap).
Сегменты памяти
Память - это просто длинный список байтов. Байты расположены упорядоченно, каждый байт имеет свой адрес. Диапазон дискретных адресов известен как адресное пространство.

Адресное пространство приложения iOS логически состоит из четырех сегментов: текста, данных, стека и кучи.
  • Текстовый сегмент содержит машинные инструкции, которые образуют исполняемый код приложения. Он создается компилятором путем перевода кода Swift в машинный код. Этот сегмент доступен только для чтения и занимает постоянное место
  • Сегмент данных хранит статические переменные Swift, константы и метаданные типов. Сюда попадают все глобальные данные, которым требуется начальное значение при запуске программы
  • В стеке хранятся временные данные: параметры метода и локальные переменные. Каждый раз, когда мы вызываем метод, в стеке выделяется новая часть памяти. Эта память освобождается при выходе из метода. За некоторыми исключениями сюда попадают все типы значений Swift
  • В куче хранятся объекты, у которых есть время жизни. Это все ссылочные типы Swift и некоторые случаи типов значений. Куча и стопка растут навстречу друг другу
Затраты на выделение и освобождение памяти в куче намного больше, чем на выделение памяти в стэке
Steve Jobs
Apple CEO
ГЛАВА II
Стек
Stack — Переменные, выделенные в стеке, хранятся непосредственно в памяти, и доступ к этой памяти очень быстрый, и ее выделение определяется при компиляции программы.

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

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

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

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

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

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

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

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

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

  • Требует потокобезопасности

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

  • Когда процесс запрашивает определенный объем памяти, куча ищет адрес памяти, который удовлетворяет этому запросу, и возвращает его процессу

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

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

  • Reference Type — у нас есть ссылка, указывающая на это место в памяти. Переменные ссылочного типа могут указывать на одни и те же данные; следовательно, операции с одной переменной могут повлиять на данные, указанные другой переменной. За него отвечает куча
Reference Type
Reference types consist of shared instances that can be passed around and referenced by multiple variables. Each instance points to the same object in memory, that is why this type has such a name. If we create a new instance that points to the existing one, then when changing any of them, the values will change in both.
class Owner {

    var name: String

    init(name: String) {
        self.name = name
    }
}

let sergey = Owner(name: "Sergey")
let richard = sergey

richard.name = "Richard"

print(sergey.name) // Richard
print(richard.name) // Richard
IsKnownUniquelyReferenced
Returns a Boolean value indicating whether the given object is known to have a single strong reference.
The isKnownUniquelyReferenced(_:) function is useful for implementing the copy-on-write optimization for the deep storage of value types. isKnownUniquelyReferenced(_:) checks only for strong references to the given object---if `object` has additional weak or unowned references, the result may still be `true`. Because weak and unowned references cannot be the only reference to an object, passing a weak or unowned reference as `object` always results in `false`.

If the instance passed as `object` is being accessed by multiple threads simultaneously, this function may still return `true`. Therefore, you must only call this function from mutating methods with appropriate thread synchronization. That will ensure that isKnownUniquelyReferenced(_:) only returns `true` when there is really one accessor, or when there is a race condition, which is already undefined behavior.
struct User {
    private var _chats = ChatHistory()

    var chats: ChatHistory {
        mutating get {
            if !isKnownUniquelyReferenced(&_chats) {
                _chats = _chats.copy() as! ChatHistory
            }

            return _chats
        }

        set {
            _chats = newValue
        }
    }
}
Identity Operators
Because classes are reference types, it's possible for multiple constants and variables to refer to the same single instance of a class behind the scenes. (The same isn't true for structures and enumerations, because they're always copied when they're assigned to a constant or variable, or passed to a function.)

It can sometimes be useful to find out whether two constants or variables refer to exactly the same instance of a class. To enable this, Swift provides two identity operators:

  • Identical to ( ===)
  • Not identical to ( !==)
Note that identical to (represented by three equals signs, or ===) doesn't mean the same thing as equal to (represented by two equals signs, or ==). Identical to means that two constants or variables of class type refer to exactly the same class instance. Equal to means that two instances are considered equal or equivalent in value, for some appropriate meaning of equal, as defined by the type's designer.

Use these operators to check whether two constants or variables refer to the same single instance:
let playerOne = PlayerClass(name: "One", health: 100, energy: 10)
let playerTwo = PlayerClass(name: "One", health: 100, energy: 10)

if (playerOne == playerTwo) {
    print("Equal")
}
if (playerOne === playerTwo) {
    print("Identical")
}
Value Type
Value types are referenced completely differently than reference types. A value type instance is an independent instance and holds its data in its own memory allocation.
struct Stone {

    enum `Type` {
        case space
        case mind
        case reality
        case power
        case time
        case soul
    }

    var type: Type // Enum - value type
    let owner: Owner // Class - reference type
}

let spaceStone = Stone(type: .space, owner: Owner(name: "Elon"))
var spaceStone2 = spaceStone // Creating a copy

// Changing the values
spaceStone2.type = .mind
spaceStone2.owner.name = "Loki"

/**
    # The value has NOT changed
*/
print(spaceStone.type) // Space
print(spaceStone2.type) // Mind

/**
    # The value has changed.
*/
print(spaceStone.owner.name) // Loki
print(spaceStone2.owner.name) // Loki
So, what's the core difference between these two types? The quick and dirty explanation is that reference types share a single copy of their data while value types keep a unique copy of their data.
Pointers
In order to pass to the function not the object itself but a pointer to it you need a keyword inout. With reference types, this is not so important, since they are already passed by reference. But if we are talking about value types this can be useful.
var zeus = Owner(name: "Zeus")
var stone = Stone(type: .reality, owner: zeus)

func update(stone: inout Stone, owner: Owner) {
    owner.name = "Vision"
    stone.type = .time // Without `inout` we can't change it
}

update(stone: &stone, owner: zeus)
Characteristics of Memory Access
There are three characteristics of memory access to consider in the context of conflicting access: whether the access is a read or a write, the duration of the access, and the location in memory being accessed. Specifically, a conflict occurs if you have two accesses that meet all of the following conditions:

  • At least one is a write access or a nonatomic access.
  • They access the same location in memory.
  • Their durations overlap.
The difference between a read and write access is usually obvious: a write access changes the location in memory, but a read access doesn't. The location in memory refers to what is being accessed — for example, a variable, constant, or property. The duration of a memory access is either instantaneous or long-term.

An access is instantaneous if it's not possible for other code to run after that access starts but before it ends. By their nature, two instantaneous accesses can't happen at the same time. Most memory access is instantaneous. For example, all the read and write accesses in the code listing below are instantaneous:
func oneMore(than number: Int) -> Int {
    return number + 1
}

var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"
However, there are several ways to access memory, called long-term accesses, that span the execution of other code. The difference between instantaneous access and long-term access is that it's possible for other code to run after a long-term access starts but before it ends, which is called overlap. A long-term access can overlap with other long-term accesses and instantaneous accesses.

Overlapping accesses appear primarily in code that uses in-out parameters in functions and methods or mutating methods of a structure.
Conflicting Access to In-Out Parameters
A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.

One consequence of this long-term write access is that you can't access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it—any access to the original creates a conflict. For example:
var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize
}

increment(&stepSize)
// Error: conflicting accesses to stepSize


One way to solve this conflict is to make an explicit copy of stepSize:

// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)

// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
In the code above, stepSize is a global variable, and it's normally accessible from within increment(_:). However, the read access to stepSize overlaps with the write access to number. As shown in the figure below, both number and stepSize refer to the same location in memory. The read and write accesses refer to the same memory and they overlap, producing a conflict.
When it comes to global variables or structures that are passed as an in-out parameter. It is necessary to ensure that there is no access conflict during the read/write operation. Usually, the compiler itself will warn about this.
How to Choose?

Use a value type when:
  • Comparing instance data with == makes sense
  • You want copies to have independent state
  • The data will be used in code across multiple threads

Use a reference type (e.g. use a class) when:
  • Comparing instance identity with === makes sense
  • You want to create shared, mutable state
ГЛАВА I
Назначение в Stack ссылочных типов:
Компилятор Swift может продвигать ссылочные типы для размещения в стеке, когда их размер фиксирован или время жизни может быть предсказано. Эта оптимизация происходит на этапе генерации SIL.

Swift Intermediate Language (SIL) - это промежуточный язык высокого уровня, ориентированный на Swift, подходящий для дальнейшего анализа и оптимизации кода 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). Это означает, что копирование происходит только в момент мутации.

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

Все становится интересно, когда мы смешиваем значения и ссылочные типы. Если структуры или перечисления содержат ссылки, они будут платить накладные расходы на подсчет ссылок пропорционально количеству ссылок, которые они содержат
    //Copy on Assignment
    let emptyStruct = EmptyStruct() //address A
    let copy = emptyStruct //address B
    
    //Copy on Write
    let array = [1,2,3] //address C
    var notACopy = array //still address C
    notACopy = [4,5,6] //now address D
    Problematic Reference Counting in Value Types With Inner References
    A fully stack allocated value type will not need reference counting, but a value type with inner references will unfortunately inherit this ability.

    Since all reference types require reference counting, increasing the amount of properties of a class of classes will not change the runtime of this algorithm, as merely increasing the reference count of the parent reference will be enough to keep it's inner references alive.

    However, value types do not naturally have a reference count. If your value type contains inner references, copying it will require increasing the reference count of it's children instead - not the first, not the second, but literally every single one of them.
    final class ClassOfClasses {
        let emptyClass = EmptyClass()
        let emptyClass2 = EmptyClass()
    }        
    
    let classOfClasses = ClassOfClasses()
    let reference = classOfClasses
    let reference2 = classOfClasses
    let reference3 = classOfClasses
    
            CFGetRetainCount(classOfClasses) // 5
            CFGetRetainCount(classOfClasses.emptyClass) // 2
            CFGetRetainCount(classOfClasses.emptyClass2) // 2
            CFGetRetainCount(classOfClasses.emptyClass3) // 2
    
    struct StructOfClasses {
        let emptyClass = EmptyClass()
        let emptyClass2 = EmptyClass()
    }
    
            let structOfClasses = StructOfClasses()
            let copy = structOfClasses
            let copy2 = structOfClasses
            let copy3 = structOfClasses
    
            // CFGetRetainCount(structOfClasses) // Doesn't compile, structs themselves don't have a reference count.
            CFGetRetainCount(structOfClasses.emptyClass) // 5
            CFGetRetainCount(structOfClasses.emptyClass2) // 5
            CFGetRetainCount(structOfClasses.emptyClass3) // 5
    Сравнение Классов и Структур
    Common things in both Structures and Classes

    • Define properties to store values
    • Define methods to provide functionality
    • Define subscripts to provide access to their values using subscript syntax
    • Define initializers to set up their initial state
    • Be extended to expand their functionality beyond a default implementation
    • Conform to protocols to provide standard functionality of a certain kind
    Additionally only in Classes

    • Inheritance enables one class to inherit the characteristics of another
    • Type casting enables you to check and interpret the type of a class instance at runtime
    • Deinitializers enable an instance of a class to free up any resources it has assigne
    • Reference counting allows more than one reference to a class instance