Skip to content

Latest commit

 

History

History
76 lines (50 loc) · 6.48 KB

File metadata and controls

76 lines (50 loc) · 6.48 KB

Side Tables (боковая таблица)

  1. Side Tables
  2. Apple Opensource Code

Side tables — это механизм для реализации слабых ссылок Swift.

Слабые ссылки указывают на side table. Unowned и Strong ссылки указывают на объект.

Объекты изначально начинаются без боковой таблицы. Объекты могут получить боковую таблицу когда:

  1. формируется слабая ссылка;
  2. также может создаваться, когда происходит переполнение сильных или бесхозных счетчиков, и он уже не помещается в поле (счетчики ссылок будут маленькими на 32-битных машинах);

Получение записи в боковой таблице - это односторонняя операция; Объект получив однажды запись в боковой таблице никогда ее не теряет - ссылка. Это предотвращает некоторые гонки потоков.

side table

Как только мы начинаем ссылаться на объект слабо (weak reference), то создается боковая таблица, и теперь объект вместо сильного счетчика ссылок хранит ссылку на боковую таблицу. Сама боковая таблица также имеет ссылку на объект.

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

Таким образом, Side Table — это просто счетчик ссылок + указатель на объект. Они объявлены в Swift Runtime следующим образом (код C ++):

class HeapObjectSideTableEntry {
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;
  // Operations to increment and decrement reference counts
}

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

  1. Слабые ссылки до Swift 4

До Swift 4, счетчики ссылок располагались до свойств класса прямо в объекте. Класс имел только два счетчика  —  weak и strong.

Проблемы

  1. В один момент времени объект с сильной ссылкой удаляется из памяти, и теперь у нас осталась только одна слабая ссылка. Что происходит в этот момент?

Данные объекта уничтожаются (деинициализируются), но память не освобождается (free), так как счетчик еще требуется хранить. В памяти остается так называемый «зомби объект», на который ссылается слабая ссылка. Только при обращении по слабой ссылке в runtime будет выполнена проверка: «зомби» (NSZombie) этот объект или нет. Если да, счетчик ссылок уменьшается.

Данный подход достаточно прозрачный, но главный минус в том, что так объекты могут долго оставаться в памяти, занимая лишнее место, хотя не несут никакой пользы.

  1. Встречался еще один достаточно критичный баг: получение (загрузка) объекта по слабой ссылке было не потокобезопасным!
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