Side tables — это механизм для реализации слабых ссылок Swift.
Слабые ссылки указывают на side table. Unowned и Strong ссылки указывают на объект.
Объекты изначально начинаются без боковой таблицы. Объекты могут получить боковую таблицу когда:
- формируется слабая ссылка;
- также может создаваться, когда происходит переполнение сильных или бесхозных счетчиков, и он уже не помещается в поле (счетчики ссылок будут маленькими на 32-битных машинах);
Получение записи в боковой таблице - это односторонняя операция; Объект получив однажды запись в боковой таблице никогда ее не теряет - ссылка. Это предотвращает некоторые гонки потоков.
Как только мы начинаем ссылаться на объект слабо (weak reference), то создается боковая таблица, и теперь объект вместо сильного счетчика ссылок хранит ссылку на боковую таблицу. Сама боковая таблица также имеет ссылку на объект.
Слабые переменные указывают на боковую таблицу объекта. Указывая не на объект, а на боковую таблицу, сам объект может быть деинициализирован и полностью деаллоцирован. Эта идея является основополагающей для понимания того, как работают слабые ссылки.
Таким образом, Side Table — это просто счетчик ссылок + указатель на объект. Они объявлены в Swift Runtime следующим образом (код C ++):
class HeapObjectSideTableEntry {
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
// Operations to increment and decrement reference counts
}
До Swift 4, счетчики ссылок располагались до свойств класса прямо в объекте. Класс имел только два счетчика — weak и strong.
- В один момент времени объект с сильной ссылкой удаляется из памяти, и теперь у нас осталась только одна слабая ссылка. Что происходит в этот момент?
Данные объекта уничтожаются (деинициализируются), но память не освобождается (free), так как счетчик еще требуется хранить. В памяти остается так называемый «зомби объект», на который ссылается слабая ссылка. Только при обращении по слабой ссылке в runtime будет выполнена проверка: «зомби» (NSZombie) этот объект или нет. Если да, счетчик ссылок уменьшается.
Данный подход достаточно прозрачный, но главный минус в том, что так объекты могут долго оставаться в памяти, занимая лишнее место, хотя не несут никакой пользы.
- Встречался еще один достаточно критичный баг: получение (загрузка) объекта по слабой ссылке было не потокобезопасным!
class Target {}
class WeakHolder {
weak var: Target?
}
for i in 0..<1000000 {
print(i)
let holder = WeakHolder()
holder.weak = Target()
dispatch_async(dispatch_get_global_queue(0, 0), {
let _ = holder.weak
})
dispatch_async(dispatch_get_global_queue(0, 0), {
let _ = holder.weak
})
}
Данный кусок кода может получить ошибку в Runtime. Суть именно в том механизме, который был рассмотрен ранее. Два потока могут одновременно обратиться к объекту по слабой ссылке. Перед тем, как получить объект, они проверяют, является ли проверяемый объект «зомби». И если оба потока получат ответ true, они отнимут счётчик и постараются освободить память. Один из них сделает это, а второй просто вызовет краш, так как попытается освободить уже освобожденный участок памяти.
Также стоит ознакомиться: Жизненный цикл объекта Swift
3.1.3.1.4.4 Weak Reference Theme | Back To iOSWiki Contents | 3.1.3.2 Garbage Collector Theme