Основы Swift / 14.1. Основы механизма подсчёта ссылок


Видео


ARC
Свифт использует автоматический подсчёт ссылок (automatic reference counting ARC) для отслеживания и управления использованием памяти Вашим приложением. В большинстве случаев это означает, что управление памятью "просто работает" в Swift, и Вам не нужно думать самим об управлении памятью. ARC автоматически высвобождает память, используемые объектами класса, когда они больше не нужны.

Однако в некоторых случаях ARC требует большей информации о взаимоотношениях между Вашим кодом для управления памятью. Эта глава описывает такие ситуации и показывает, как Вы можете включить ARC для управления всей памятью Вашего приложения. Использование ARC в Swift очень походе на то, что описано в ARC для Objective-C.

Подсчёт ссылок используется только для объектов классов. Структуры и перечисления - это типы-значения, а не ссылочные, и они не хранятся и не передаются по ссылке.
Как работает ARC
Всякий раз, когда Вы создаёте новый экземпляр класса, ARC аплодирует блок памяти для хранения информации об этом объекте. Эта память содержит информацию о типе этого объекта вместе со всеми значениями хранимых свойств для этого объекта.

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

Однако, если ARC деаллоцирует объект, который всё ещё используется, то больше не будет возможности получить доступ к свойствам объекта или вызвать методы этого объекта. Действительно, если Вы попытаетесь получить доступ к такому объекту, то Ваше приложение скорее всего упадёт.

Чтобы убедиться, что объекты ещё нужны, ARC отслеживает, сколько свойств, констант или переменных всё ещё ссылаются на каждый объект класса. ARC не деаллоциирует объект до тех пор, пока на него существует хотя бы одна активная ссылка.

Чтобы сделать это возможным, когда бы Вы не присваивали объект класса свойству, константу или переменной, то это свойство , константа или переменная создаёт сильную ссылку на объект. Такая ссылка называется "сильной", так она связана сильно с объектом и не позволяет ему быть деаллоцированным, пока она существует.
ARC в действии
Здесь приведён пример того, как ARC работает. Этот пример начинается с простого класса Person, который определяет хранимое свойство name:
@{14.1\1\1}
Класс Person имеет инициализатор, который устанавливает значение свойства name и выводит сообщение для индикации того, что инициализация происходит. Класс Person также имеет деструктор, который выводит сообщение, когда объект класса деаллоцируется.
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}
@{14.1\1\2}
Следующий пример кода задаёт три переменные типа Person?, которые используются для установки множества ссылок на новый объект типа Person. Так как эти переменные имеют опциональный тип (Person?, а не Person), то они автоматически инициализируются значением nil, а не какой-то ссылкой на объект типа Person.
var reference1: Person?
var reference2: Person?
var reference3: Person?
@{14.1\1\3}
Вы можете создать новый объект типа Person и присвоить ему одной из тех переменных:
reference1 = Person(name: "John Appleseed")
// Выведет "John Appleseed is being initialized"
Заметьте, что сообщение "John Appleseed is being initialized" выводится в момент, когда Вы вызываете инициализатор класса Person. Это подверждает, что инициализация имела место быть.

Так как объект типа Person был присвоен переменной variable1, теперь существует сильная ссылка от reference1 к новому объекту Person. Так как существует хотя быть одна сильная ссылка, то ARC будет гарантированно хранить этот объект Person в памяти и не будет его деаллоцировать.
@{14.1\1\4}
Если Вы присвоите тот же объект типа Person двум другим переменным, то будет создано ещё две сильных ссылки:
reference2 = reference1
reference3 = reference1
Теперь существуют три сильные ссылки на один объект типа Person.
@{14.1\1\5}
Если Вы разобьете две из этих сильных ссылки (включая оригинальную), присвоив nil двум из этих переменных, то останется одна сильная ссылка, а объект Person не будет деаллоцирован:
reference1 = nil
reference2 = nil
@{14.1\1\6}
ARC не деаллоцирует объект Person, пока третья и последняя сильная ссылка не будет разбита, в момент чего станет ясно, что Вы больше не используете объект типа Person:
reference3 = nil
// Выведет "John Appleseed is being deinitialized"
Циклы сильных ссылок между экземплярами классов
В примере выше ARC способен отследить количество ссылок на новый объект типа Person, который Вы создаёте, и деаллоцировать этот объект Person, когда он больше не будет нужен.

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

Вы можете разрешить циклы сильных ссылок, определив взаимоотношения между классами как слабые или невладеющие вместо сильных. Однако прежде, чем Вы изучите, как разрешать цикл сильных ссылок, будет полезным понять, как это цикл возникает.
@{14.1\2\1}
Здесь приведён пример того, как по неосторожности может быть создан цикл сильных ссылок. Этот пример определяет два класса с названиями Person и Apartment, которые моделируют апартаменты и их постояльцев:
class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}
Каждый объект типа Person имеет свойство name типа String и опциональное свойство apartament с изначальным значением nil. Свойство apartament опционально, так как человек не всегда может иметь апартаменты.

Аналогично, каждый объект типа Apartment имеет свойство unit типа String и опциональное значение tenant изначально заданное в nil. Свойство постояльца опциоально, потому что апартаменты не всегда могут иметь постояльца.

Оба эти класса имеют деструкторы, которые печатают факт того, что объект класса был деинициализирован. Это позволяет Вам видеть, деалоцировались ли объекты Person и Apartament, как это ожидалось.
@{14.1\2\2}
Следующий пример кода определяет две переменных опционального типа с названиями john и unit4A, которым ниже будут заданы конкретные значения типа Person и Apartment. Обе этих переменных имеют изначальное значение nil, что достигается за счёт их опциональности:
var john: Person?
var unit4A: Apartment?
@{14.1\2\3}
Теперь Вы можете создать конкретные объекты типа Person и Apartament и присвоить их переменных john и unit4A:
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
Здесь представлено, как сильные ссылки выглядят после создания и присвоения этих двух объектов. Переменная john теперь имеет сильную ссылку на новый объект Person, а переменная unit4A имеет сильную ссылку на новый объект Apartament:

@{14.1\2\4}
Теперь Вы можете соединить эти два объекта вместе, так чтобы человек имел апартаменты, а апартаменты бы имели постояльца. Заметьте, что восклицательный знак (!) используется для распаковки и доступа к объектам, хранимым внутри опциональных переменных john и unit4A, так чтобы свойства этих объектов могли быть заданы:
john!.apartment = unit4A
unit4A!.tenant = john
Здесь представлено, как сильные ссылки выглядят после того, как Вы слинкуете эти два объекта вместе:

@{14.1\2\5}
К сожалению, связывание этих двух объектов создаёт сильный цикл ссылок между ними. Объект Person имеет теперь сильную ссылку на объект Apartment, а объект Apartment имеет сильную ссылку на объект Person. Следовательно, когда Вы разобьете сильные ссылки, удерживаемые переменными john и unit4A, то количество ссылок не упадёт в нуль, а объекты не будут деалоцированы ARC:
john = nil
unit4A = nil
Заметьте, что не было вызвано никакого инициализатора, когда Вы установили эти две переменных в nil. Цикл сильных ссылок мешает объектам Person и Apartment быть деалоицированными, что вызывает утечку памяти в Вашем приложении.

Сильные ссылки между объектом Person и Apartment остаются и не могут быть разбиты.

Здесь представлено, как выглядят сильные ссылки после того, как john и unit4A переменные установлены в nil: