Жизненный цикл объектов в Delphi. Часть 2. Android, iOS. Что же использовать Destroy, Free, FreeAndNil или DisposeOf?

native_iOS_SpinBox

Продолжаем тему жизненного цикла объектов в мире Delphi, но в этой части рассматриваем эту тему в рамках мобильных платформ Android и iOS.

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


Введение

Появление поддержки мобильных платформ в Delphi было сопровождено важными изменениями в модели жизненного цикла объектов. В результате чего, то, что раньше удалялось, могло стать «не убиваемым», а то что раньше не удалялось, могло спокойно стать сразу убиваемым. Это привело к полному непониманию, как убить объект, и зачастую к непереносимости кода с настольных платформ. В этой статье разберемся с этой темой подробнее.

Чтобы прочувствовать проблему, давайте взглянем на простой кусок кода, создающий анонимный поток и выводящий в лог сообщение:

procedure TForm2.Button1Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(
    procedure
    begin
      Log.d('Hello from anonymous thread');
    end).Start;
end;

Вопросы:

  1. Выведется ли сообщение на всех платформах?
  2. Будет ли удален экземпляр объекта анонимного потока?

Ответы:

  1. Нет. Выведется на Windows, OSX. На Android, iOS в большинстве в 99% случаев нет.
  2. Да, поток будет удален на всех платформах. Но на Windows, OSX будет удален после работы потока. А на iOS и Android будет удален до старта.

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

Но теперь давайте разберемся со всем по порядку.

Что такое ARC?

Итак, если дословно, то ARC — это Automatic Reference Counting или же Автоматический подсчет ссылок. Суть простая, теперь каждый объект в делфи имеет счетчик ссылок. Он введен на уровне класса TObject, поэтому вы можете получить к нему доступ в любом объекте, и вы можете получить к нему доступ на чтение. Сразу отмечу, что этот счетчик только появляется на мобильных платформах, с поддержкой ARC, на всех остальных его нет, о чем нам говорит директива условной компиляции AUTOREFCOUNT.

Часть декларации TObject. На скриншоте отображено объявление свойства RefCount, доступного только на мобильных платформах

Часть декларации TObject. На скриншоте отображено объявление свойства RefCount, доступного только на мобильных платформах

Итак:

  1. Когда вы присваиваете ссылку переменной, полю, свойству, передаете объект через параметр, компилятор увеличивает счетчик ссылок на 1.
  2. Как только, заканчивается область видимости переменной, зануливается переменная, то компилятор автоматически уменьшает счетчик ссылок на 1.
  3. Как только счетчик ссылок равен 1, объект автоматически уничтожается.

Это краткое объяснение на пальцах, что из себя представляет ARС.

Зачем нужен ARC?

Это, наверное, самый главный вопрос.

Итак, первоначальная цель ARC — это сократить код по удалению объектов. Избавить разработчика от необходимости вставлять код по удалению объектов и переложить эту задачу на плечи компилятору, при этом сохранив производительность управления памятью.

Пример: В этом простом надуманном примере, не требуется удалять объект Apple, компилятор сделает за нас это автоматически. И в момент компиляции за нас подставит системный вызов деструктора. Таким образом, объект будет гарантированно удален, а код становится проще.

var
  Apple: TApple;
begin
  Apple := TApple.Create;
  Apple.Eat;
end;

Помимо этого, ARC за нас использует блок try-finally-end, тем самым гарантируя, что объект Apple будет удален в любом случае, даже если в коде Eat возникнет исключение.

Однако, за такой простотой, к сожалению, скрывается ряд проблем, которые делфи вводит вместе с арк для своих разработчиков. Об этом мы поговорим в этой статье позже. Ну а теперь: Камень в огород Delphi.

ARC был введен в семействе Clang для Objective-C. Именно там он и появился.

Objective-C компилятор дает разработчикам выбрать: хотят ли разработчики использовать ARC или нет.

В Delphi же такой возможности нет, поэтому приходится подстраиваться под мир Delphi, ругаться на новые версии и перелопачивать свои тонны кода, чтобы подогнать свой код под «новые стандарты» в попытках разобраться, почему то, что удалялось много лет, перестает работать и удаляться в новых платформах. Но об этом позже.

ARC — это сборщик мусора?

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

Сборщик мусора — это процесс (сторонняя программа), которая в определенные момент времени останавливает ваше приложение и строит граф достижимости объектов. Если объект не достижим, то он помечает на будущее удаление, но не удаляется в этот же момент времени. Все недостижимые объекты удаляются сборщиком мусора. Но опять же момент времени заранее не известен. Сборщик мусора более ресурсоемкий и менее производительный, но при этом дает более удобную модель управления памятью.

Что же происходит под капотом в Delphi при использовании ARC?

Ссылки

Счетчик ссылок

Итак, счетчик ссылок введен на уровне TObject, давайте разберемся, как и когда он изменяется.

Пример 1: Пишем простой обработчик нажатия кнопки и выводим после каждого действия значение счетчика ссылок. Этот пример демонстрирует, как увеличивается счетчик ссылок.

procedure TForm2.Button2Click(Sender: TObject);
var
  MyObject1: TObject;
  MyObject2: TObject;
begin
  MyObject1 := TObject.Create;
  MemoLog.Lines.Add('MyObject1.RefCount=' + MyObject1.RefCount.ToString);

  MyObject2 := MyObject1;
  MemoLog.Lines.Add('MyObject1.RefCount=' + MyObject1.RefCount.ToString);
  MemoLog.Lines.Add('MyObject2.RefCount=' + MyObject2.RefCount.ToString);

  MyObject1 := nil;
  MemoLog.Lines.Add('MyObject2.RefCount=' + MyObject2.RefCount.ToString);

  MyObject2 := nil;
end;

Запустив приложение на андроиде,  мы увидим, что при любом присвоении ссылки компилятор автоматически увеличивает счетчик ссылок, а при занулении ссылки, соответственно — уменьшает. Как только счетчик ссылок равен 0, объект автоматически удаляется.

Вывод программы в лог. Демонстрация изменения счетчика ссылок при присваивании

Если мы рассмотрим процессорные команды, то мы наглядно увидим, что компилятор добавляет системные вызовы методов по управлению ссылками (Предварительно для упрощения, я убрал из кода выше логирование счетчика RefCount):

Демонстрация вставки компилятором системных вызовов InstCopy, InstClear при работе со ссылками в ARC

Демонстрация вставки компилятором системных вызовов InstCopy, InstClear при работе со ссылками в ARC

Зеленым отмечены вызовы _InstCopy. Этот метод отвечает за увеличение счетчика ссылок, вызывая TObject.__ObjAddRef.

Код системной процедуры по копированию ссылок под ARC

Код системной процедуры по копированию ссылок под ARC

Красным отмечены вызовы _InstClear, которые уменьшают счетчик ссылок вызывая метод TObject.__ObjRelease.

Код системной процедуры по очистке ссылки под ARC

Код системной процедуры по очистке ссылки под ARC

Рассмотрим пример посложнее.

Пример 2: В этом примере создаем свой класс объекта, задача которого вывести в лог информацию, когда объект создается и когда разрушается. Дополнительно будем выводить информацию о счетчике ссылок компонента TMemo. В этом примере мы увидим, что объект удаляется сразу же, как только на него нету ссылок.

type
  TMyObject = class(TObject)
  public
    FMemo: TMemo;
  protected
    constructor Create(AMemo: TMemo);
    destructor Destroy; override;
  end;

procedure TForm2.Button3Click(Sender: TObject);
var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create(MemoLog);
  MyObject := nil;
end;

{ TMyObject }

constructor TMyObject.Create(AMemo: TMemo);
begin
  Log.d('TMyObject.Create: Begin AMemo.RefCount=', [AMemo.RefCount]);
  FMemo := AMemo;
  Log.d('TMyObject.Create: End AMemo.RefCount=', [AMemo.RefCount]);
end;

destructor TMyObject.Destroy;
begin
  FMemo.Lines.Add('TMyObject.Destroy: RefCount=' + RefCount.ToString);
  inherited;
end;

Запустив приложение, в логе мы увидим следующее:

  1. Первая строчка говорит нам, что мемо используется на прямую в 5 местах, что соответствует 5 ссылкам.
  2. Вторая строчка указывает, что при передаче мемо без использования const, компилятор так же автоматически увеличивает счетчик ссылок, что мы и видим. Теперь у мемо 6 ссылок.
  3. Третья строчка в логе, как и ожидается, указывает, что поскольку мы присвоили мемо в поле объекта, то счетчик логически увеличился на 1 и равен 7.
  4. Четвертая строчка указывает, что вызывается деструктор нашего объекта. И тут стоит необычное значение -1073741824. Это не ошибка. Это специальное маскированное значение, в котором закодировано, что объект не имеет ссылок и подлежит удалению.
  5. Последняя строчка говорит нам, что деструктор был вызван после обнулении ссылки MyObject := nil. И счетчик ссылок на мемо опять равен 5, так как две ссылки на мемо были обнулены: первая — из поля, удаленного объекта, вторая — при выходе из конструктора (см. пункт 2)

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

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

Циклические, слабые и сильные ссылки

Циклические ссылки — это замкнутая цепочка «сильных» ссылок в графе ссылок объектов. Другими словами, если из двух объектов можно по ссылкам добраться друг до друга, то они являются неявно циклически зависимыми друг от друга. Рассмотрим такой пример.

Демонстрация циклических ссылок, в результате которых объекты не будут уничтожены

Демонстрация циклических ссылок, в результате которых объекты не будут уничтожены

Пример 3: Дополним наш класс TMyObject, добавив поле OwnedObject для хранения ссылки на другой объект:

type
  TMyObject = class(TObject)
  private
    FMemo: TMemo;
    FOwnedObject: TMyObject;
  protected
    constructor Create(AMemo: TMemo);
    destructor Destroy; override;
    property OwnedObject: TMyObject read FOwnedObject write FOwnedObject;
  end;

Теперь сформируем циклические ссылки. Сделаем два объекта и присвоим ссылки друг на друга.

procedure TForm2.Button4Click(Sender: TObject);
var
  Object1: TMyObject;
  Object2: TMyObject;
begin
  Object1 := TMyObject.Create(MemoLog);
  Object2 := TMyObject.Create(MemoLog);

  // Делаем циклическую зависимость.
  Object1.OwnedObject := Object2;
  Object2.OwnedObject := Object1;

  MemoLog.Lines.Add('Object1.RefCount=' + Object1.RefCount.ToString);
  MemoLog.Lines.Add('Object2.RefCount=' + Object2.RefCount.ToString);

  // Эти действия не приведут к удалению Object1 и Object2, так как оба объекта имеют одну ссылку друг на друга
  Object1 := nil;
  Object2 := nil;
end;

После запуска приложения, мы видим в логе, что после окончания обработчика не был вызван деструктор ни у Object1, ни у Object2. Это связано с тем, что реально у нас появилась утечка памяти. Мы уже не имеем доступа к Object1 и Object2, но из-за того, что они ссылаются друг на друга счетчик ссылок никогда уже не будет равен 0, а значит ни один из этих объектов не будет удален.

Как быть в этом случае? Для разруливания такой ситуации есть понятие сильной и слабой ссылки.

  1. Сильная ссылка  (Strong) — та, которая влияет на изменение счетчика ссылок.
  2. Слабая ссылка (Weak) — та, которая НЕ влияет на изменение счетчика ссылок.

По умолчанию в делфи все ссылки сильные, чтобы пометить ссылку слабой, нужно дописать в квадратных скобках слово Weak. Помечать можно:

  1. Локальные, глобальные переменные.
  2. Поля объектов.
  3. Параметры методов, функций и процедур.
  4. Поля записей.
  5. и тд
[Weak] FMemo: TMemo;

type
  TMyRect = record
    StrongField: TObject;
    [Weak] WeakField: TObject;
  end;

procedure Foo([Weak] AParam: TObject);

При присваивании объекта в слабую ссылку, счетчик ссылок не меняется.

Пример 4: Пометим поля: FMemo и FOwnedObject у класса TMyObject, как Weak. Тем самым, запретим менять счетчик ссылок, когда мы сохраняем ссылки на те объекты, которые мы не создавали.

type
  TMyObject = class(TObject)
  private
    [Weak] FMemo: TMemo;
    [Weak] FOwnedObject: TMyObject;
  protected
    constructor Create(AMemo: TMemo);
    destructor Destroy; override;
    property OwnedObject: TMyObject read FOwnedObject write FOwnedObject;
  end;

Теперь после запуска нашего приложения, в логе уже будут другие строчки:

Теперь деструкторы сработали и наши объекты удалились.

Обнуление слабых ссылок

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

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

type
  TMyObject1 = class
  private
    [Weak] FOwnedObject: TObject;
  public
    constructor Create(AOwnedObject: TObject);
    property OwnedObject: TObject read FOwnedObject;
  end;

procedure TForm2.Button5Click(Sender: TObject);
var
  Object1: TMyObject1;
  Object2: TObject;
begin
  Object2 := TObject.Create;
  Object1 := TMyObject1.Create(Object2);
  MemoLog.Lines.Add('Object1.OwnedObject = ' + Integer(Pointer(Object1.OwnedObject)).ToString);

  // Удаляем Второй объект, автоматически обнуляется слабая ссылка у Object1.OwnedObject
  Object2 := nil;
  MemoLog.Lines.Add('Object1.OwnedObject = ' + Integer(Pointer(Object1.OwnedObject)).ToString);
end;

Если вы запустите это приложение, то увидите в логе, что после удаления второго объекта, слабая ссылка на него будет автоматически обнулена:

Какие ссылки помечать, как слабые?

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

Случай 1. Если объект 1, создает объект 2, то объект 1 должен иметь сильную ссылку  на объект 2. Если объект 2, не создает объект 1, а получает его извне, то есть фактически Объект 1 не принадлежит Объекту 2, а лишь используется, то в этом случае ссылка должна помечаться, как слабая:

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

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

Поскольку Object 1 создает Object 2, то Object 1 и должен в своем деструкторе уничтожать Object 2.

Случай 2. Если объект 1 не создает Объект 2, а объект 2 не создает объект 1, то обе ссылки должны быть помечены, как слабые.

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

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

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

Ну а теперь перейдем непосредственно к способам удаления объектов.

Способы удаления объектов

Итак, существует несколько способов удалить объект.

Способ 1. Зануление ссылки

Этот способ мы использовали в течении этой статьи. Главная идея, как только счетчик ссылок равен 0, объект автоматически уничтожается. Поэтому обнуляем ссылки, тем самым уменьшаем счетчик ссылок. Как только счетчик равен 0, объект удаляется.

Здесь есть один важный нюанс: «Такой подход не кроссплатформенен между настольными и мобильными платформами». То есть на настольных платформах будет утечка памяти при использовании этого подхода. Поэтому, несмотря на то, что этот способ можно использовать на мобильных платформах, я бы рекомендовал использовать способы 2 и 3.

Достоинства:

  1. Отвечает концепции ARC, позволяя опустить код по удалению объекта.

Недостатки:

  1. Не кроссплатформенен. На настольных платформах приведет к утечке памяти.

Способ 2. Использование Free

Итак, мы подобрались к самому важному методу TObject.Free, который, как мы помним, на настольных платформах приводит к уничтожению объекта. В ARC — это не так.

Под ARC:

  1. Компилятор при вызове метода Free зануливает ссылку на объект,
  2. Идет проверка счетчика ссылок.
    1. Если он равен 0, то объект уничтожается (вызывается деструктор Destroy и метод FreeInstance)
    2. Если счетчик ссылок не равен 0 (то есть объект где-то еще используется), то объект не удаляется.

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

Использование этого метода требует правильной архитектуры приложения. А именно четкое разделение кто кем владеет. Только при четкой организации этого, можно добиться аналогичной работы метода Free, как и на настольных платформах.

Недостатки:

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

Достоинства:

  1. Способствует хорошей правильной системе объектов в вашем приложении. Придется подумать о том, где объекты хранятся, кто создает. Кто кому и кем является (владельцем или просто клиентом).
  2. При правильной архитектуре, это решение кроссплатформенно. Но только при условии, что вы четко понимаете, кто кем владеет.

Способ 3. Использование FreeAndNil

Этот способ аналогичен методу Free на мобильных платформах. Поскольку использование метода Free так же обнуляет ссылку. Несмотря на то, что FreeAndNil по сути делает повторное обнуление ссылки и этот метод я рекомендую на настольных платформах, то я так же рекомендую его использовать и под ARC в совокупности с правильной организации связей между объектами. Поскольку это решение будет кроссплатформенно и поможет так же поймать ошибки на настольных платформах (см. 1 часть статьи).

Способ 4. Использование DisposeOf

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

Специально для половинчатой обратной совместимости был введен новый метод TObject.DisposeOf, который, по сути, просто игнорирует счетчик ссылок и вызывается деструктор. При этом после отработки деструктора, память, выделенная под объект, не удаляется до тех пор, пока счетчик ссылок не будет равен 0. Другими словами, DisposeOf удаляет объект наполовину и переводит его в состояние зомби: Внутренностей объекта уже нет, а место в памяти он пока еще занимает.

Демонстрация реализации DisposeOf

Как видите, этот метод:

  1. Напрямую вызывает BeforeDestruction.
  2. Напрямую вызывает деструктор.
  3. На момент удаления объекта помечает себя как удаленный, чтобы избежать многопоточного повторного удаления.
  4. На настольных платформах аналогичен Free.

Недостатки:

  1. Костыль для обратной совместимости на половину. Наполовину — потому что, несмотря на то, что объект как бы удалился, память все равно не очищается полностью. И память будет течь незаметно. Так как нету никакой гарантии, что ваше старое приложение в принципе удаляет корректно все объекты.

Достоинства:

  1. Как и любой костыль, костыль создан для решения проблемы. В данном случае он пытается решить проблему удаления объектов
  2. Если вам досталась чужая библиотека, написанная давно и не поддерживаемая ARC, то DisposeOf это безусловно хороший способ задушить утечки в сторонних библиотеках.

Вопрос-ответ

Как удалить контрол, фрейм с формы?

Пусть у нас есть две кнопки. По нажатию на первую, нам нужно удалить кнопку Button2.

Решение:

Чтобы удалить кнопку, нам нужно обнулить все ссылки. В FireMonkey обычно каждый визуальный компонент имеет несколько ссылок:

  1. Каждый компонент имеет родителя и входит в список дочерних компонентов родителя. Это добавляет как минимум одну дополнительную ссылку.
  2. Каждый компонент с фокусом, как минимум получает одну дополнительную ссылку.
  3. Некоторые компоненты могут использоваться по ссылкам в других, но это зависит уже от реализации.

Это значит, что если мы даже просто обнулим Button2 переменную, то кнопка останется на форме, так как эта кнопка входит в список дочерних компонентов формы, что добавляет ей одну строгую ссылку. Поэтому обычным вызовом метода Free, кнопка не удалится.

procedure TForm2.Button1Click(Sender: TObject);
begin
  Button2.Free;
end;

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

procedure TForm2.Button1Click(Sender: TObject);
begin
  Button2.DisposeOf;
end;

Почему анонимный поток не стартует?

Вернемся к примеру из введения. Как сделать анонимный поток и добиться его выполнения.

Решение: Чтобы этого добиться, нужно сохранить экземпляр анонимного потока в глобальную переменную или поле объекта, тем самым гарантировав, что у него счетчик ссылок будет равен 1.

var
  GlobalThread : TThread;

procedure TForm2.Button1Click(Sender: TObject);
begin
  GlobalThread := TThread.CreateAnonymousThread(
    procedure
    begin
      Log.d('Hello from anonymous thread');
    end);
  GlobalThread.Start;
end;

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

Как создать список компонентов и потом их удалить?

Требуется программно создать набор компонентов, а потом их удалить.

Решение: Допустим, мы создаем окружности и помещаем их в контейнер Layout следующим способом:

procedure TForm2.Button1Click(Sender: TObject);
var
  MyCircle: TCircle;
  I: Integer;
begin
  for I := 0 to 10 do
  begin
    MyCircle := TCircle.Create(Layout1);
    MyCircle.Parent := Layout1;
    MyCircle.Width := 20;
    MyCircle.Height := 20;
    MyCircle.Align := TAlignLayout.Left;
    MyCircle.Fill.Color := TAlphaColors.Red;
  end;
end;

Тогда, чтобы удалить круги, можно воспользоваться одним из двух способов:

procedure TForm2.Button2Click(Sender: TObject);
var
  MyCircle: TFmxObject;
begin
  // Способ 1
  while Layout1.ChildrenCount > 0 do
  begin
    MyCircle := Layout1.Children[0];
    // Обязательно убираем объект из контейнера, иначе останется сильная ссылка
    Layout1.RemoveObject(MyCircle);
    MyCircle.DisposeOf;
  end;

  // Способ 2
  Layout1.DeleteChildren;
end;

Если вы сохраняете ваши компоненты в дополнительные списки, то необходимо так же удалить этот объект из всех ваших списков.

Выводы

Кратко подведем выжимку тезисов из этой статьи:

  1. На мобильных платформах каждый объект имеет счетчик ссылок.
  2. Любое присваивание объекта переменной strong ссылки  увеличивает счетчик ссылок.
  3. Все ссылки в делфи по умолчанию сильные strong.
  4. Любое присваивание объекта переменной weak ссылки не меняет значение счетчика ссылок
  5. Передача объекта в качестве const параметра не изменяет счетчик ссылок.
  6. Передача объекта в качестве weak параметра не изменяет счетчика ссылок.
  7. Использование указателя на объект, не изменяет счетчика ссылок.
  8. Как только счетчик ссылок равен 0, объект сразу же удаляется.
  9. Если один объект порождает другой, то старайтесь, чтобы он держал на него сильную ссылку.
  10. Если один объект использует другой и получает его извне, то он должен держать на него слабую ссылку.
  11. Если объект уничтожается, то все слабые ссылки на него тут же обнуляются.

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *