Pull to refresh

Swift 4 — слабые ссылки

Reading time 5 min
Views 16K
Original author: Mike Ash
Вскоре после публикации исходного кода Swift, я написал статью о том как реализованы слабые ссылки. Время не стоит на месте и всё меняется, реализация слабых ссылок в Swift тоже. Сегодня я расскажу о новой реализации и сравню ее со старой. Спасибо Guillaume Lessard за идею для поста.

Старая реализация


Для тех из вас кто не помнит старую реализацию и кому лень пробежаться по предыдущей статье я кратко опишу ее здесь.

В старой версии Swift объекты имели 2 счетчика ссылок: счетчик для сильных ссылок и счетчик для слабых. Когда счетчик сильных ссылок становится равен нулю, а слабых еще нет — объект уничтожается, но память не освобождается. Поэтому в памяти остается так называемый “зомби объект” на который ссылается слабая ссылка.

Когда слабая ссылка загружается, среда времени выполнения (runtime) проверяет является ли объект “зомби”. Если он “зомби” то runtime обнуляет слабую ссылку и уменьшает счетчик слабых ссылок на 1. Когда счетчик слабых ссылок достигает 0 — память освобождается (deallocated). Это означает, что полностью объект удаляется только тогда, когда все слабые ссылки на него обнуляются.

Мне нравилась простота этой реализации, но были у нее и недостатки:

  1. Зомби объекты могли оставаться в памяти долгое время. Классы с большим количеством свойств (property) в этом случае могут сильно расходовать память.
  2. Другая проблема, которую я обнаружил после написания статьи, была в том, что реализация оказалась не потокобезопасной для одновременного чтения. Ой-ёй! Это было исправлено, но дискуссия возникшая вокруг этой проблемы подняла вопрос о том, чтобы создать новую реализацию слабых ссылок, более устойчивую к подобным проблемам.

Данные объекта


Объект в Swift состоит из множества кусочков данных.

Во-первых, очевидно, что он содержит все свойства (properties) которые объявлены в исходном коде класса. Это то, что напрямую описывается программистом.

Во-вторых, он ссылается на объект класса. Это нужно для динамической диспетчеризации (то что в C++ называется виртуальными методами) и для реализации встроенной функции “type(of:)” для определения типа объекта в runtime. Это в большей степени скрыто от программиста, хотя виртуальные методы и наличие функции “type(of:)” само — собой предполагают наличие этого механизма.

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

В-четвертых, у вас есть дополнительная информация хранимая для Objective-C runtime, типа списка всех слабых ссылок и associated objects. (Реализация слабых ссылок для Objective-C отслеживает каждую ссылку в отдельности)

Где же хранятся все эти данные?

В Objective-C, указатель на класс (isa) и свойства (properties) хранятся непосредственно в памяти объекта. Указатель на объект класса занимает первый кусочек памяти, за ним идут переменные экземпляра (ivar). Дополнительная информация хранится во внешних таблицах. Когда вам нужен associated object, runtime находит его в большой хеш таблице, ключами которой являются адреса объектов. Это работает медленно и требует захвата объекта потоком (блокировки) для синхронизации доступа к нему. Счетчик ссылок иногда хранится в памяти объекта, а иногда во внешней таблице, в зависимости от версии ОС и архитектуры процессора.

Абстрагируясь от того как это реализовано, давайте зададим себе вопрос: как это следует реализовать?

Каждый способ расположения в памяти имеет свои плюсы и минусы. Вы можете быстро получить доступ к данным которые хранятся внутри памяти объекта, но это расходует дополнительную память. Доступ к данным находящимся во внешних таблицах занимает больше времени, но не расходует дополнительную память для объектов которые не нуждаются в них. Это одна из причин почему Objective-C изначально не хранил счетчик ссылок непосредственно в объекте. Objective-C был разработан во времена когда компьютеры были сильно ограничены в памяти. Большинство объектов в типичном Objective-C коде имеют только одного владельца, можно представить что их счетчик равен 1. Выделять 4 байта на каждый объект который все время хранит 1 было бы расточительством. Во внешней таблице мы можем заменить общее значение 1 отсутствием записи вовсе, что будет экономить память.

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

Доступ к свойствам (property) ожидаемо быстр. Наличие их определяется на этапе компиляции. Объекты без свойств могут не выделять для них памяти, даже если они обычно хранятся в памяти объекта.

Побочные таблицы (Side Table)


В новой реализации Swift было введено понятие побочной таблицы.
Побочная таблица — это дополнительная область памяти в которой хранится дополнительная информация об объекте. Она опциональна, это означает что она не обязательно будет присутствовать у объекта. Объекты которые нуждаются в ней несут некоторые потери в производительности, а те которым она не нужна не испытывают ее “нагрузки”.

У каждого объекта есть указатель на свою побочную таблицу, как и у побочной таблицы есть указатель на объект. Побочные таблицы могут хранить дополнительную информацию — такую как associated object. Чтобы избежать дополнительных затрат в виде 8 байт на указатель на побочную таблицу, Swift прибегает к изящной оптимизации. Изначально первым полем в объекте хранится указатель на клaсс, а вторым — счетчик ссылок. Когда объект нуждается в сторонней таблице, это второе поле начинает использоваться для хранения указателя на нее. Так как в любом случае объекту нужны счетчики ссылок, то они располагаются теперь в сторонней таблице. Чтобы различать два этих случая, во втором поле зарезервирован определенный бит, который определяет, хранит ли поле счетчик ссылок или указатель на побочную таблицу.

Благодаря побочным таблицам Swift реализует старую систему подсчета ссылок при этом избавляясь от ее недостатков. Вместо того чтобы указывать на сам объект, слабые ссылки теперь указывают на побочную таблицу. Так как побочные таблицы занимают немного места — мы избавляемся от проблемы растраты памяти из-за слабых ссылок на большие объекты. Это наталкивает на простое решение проблемы потокобезопасности: не обнулять слабые ссылки. Так как побочные таблицы малы в размере, мы можем сохранять слабые ссылки на нее пока сами ссылки не будут перезаписаны или уничтожены.

Я должен упомянуть что текущая реализация побочных таблиц содержит только счетчики ссылок и указатель на сам объект. Дополнительные применения, такие как реализация associated object для Swift, остаются пока гипотетическими. Swift пока не имеет встроенного функционала для associated object, а Objective-C все еще использует глобальные таблицы.

Описанный подход имеет большой потенциал и мы возможно скоро увидим associated objects в Swift. Я надеюсь что это сделает возможным хранение свойств (properties) в расширениях классов и другие прикольные фичи.

Код


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

Ссылки относятся к определенным версиям коммитов, чтобы люди читающие это в будущем имели представление о том что я описываю. Если вы ищете последнюю имплементацию убедитесь что переключились на master или обновили репозиторий после перехода по ссылке.

Заключение


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

От переводчика:
Если вы заметили какую-то ошибку (техническую или нет), просьба написать в комментарии по этому поводу.
Спасибо.
Tags:
Hubs:
+7
Comments 3
Comments Comments 3

Articles