Как освободить компонент в Android/iOS

Я динамически создаю TEdit в форме в Android:

edit := TEdit.Create(Self);

Я хочу освободить его, используя edit.Free, но он все еще остается в форме.

Этот код отлично работает на win32, но не удался на Android.

То же самое происходит не только для TEdit, но и для любого компонента, использующего Android или iOS.

Ответ 1

Короткий ответ

Существует два правила, которые должны соблюдаться при выпуске любого объекта TComponent потомка под компиляторами Delphi ARC (в настоящее время Android и iOS):

  • с использованием DisposeOf является обязательным, независимо от объекта, имеющего владельца, или нет
  • в деструкторах или в тех случаях, когда ссылка не выходит за рамки сразу после вызова DisposeOf, ссылка объекта также должна быть установлена ​​на nil (подробное объяснение в Pitfalls)

Возможно, имеет смысл иметь метод DisposeOfAndNil, но ARC делает его намного сложнее, чем в случае старого метода FreeAndNil, и я бы предложил использовать обычную последовательность DisposeOf - nil, чтобы избежать дополнительных проблем:

Component.DisposeOf;
Component := nil;

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

DisposeOf в контексте управления памятью ARC

DisposeOf прерывает ARC. Это нарушает золотое правило ARC Любая ссылка на объект может быть либо действительной ссылкой на объект, либо nil, и вводит ссылку на объект с тремя состояниями - , расположенную "zombie" .

Любой, кто пытается понять управление памятью ARC, должен смотреть на DisposeOf как дополнение, которое просто решает проблемы с инфраструктурой Delphi, а не концепцию, которая действительно принадлежит самому ARC.

Почему DisposeOf существует в компиляторах Delphi ARC?

TComponent класс (и все его потомки) был разработан с учетом ручного управления памятью. Он использует механизм уведомления, который несовместим с управлением памятью ARC, потому что он основан на нарушении сильных эталонных циклов в деструкторе. Поскольку TComponent - это один из базовых классов, на которые полагаются фреймворки Delphi, он должен иметь возможность нормально функционировать при управлении памятью ARC.

Помимо механизма Free Notification существуют и другие аналогичные конструкции в инфраструктурах Delphi, подходящие для ручного управления памятью, потому что они полагаются на нарушение сильных эталонных циклов в деструкторе, но эти конструкции не подходят для ARC.

DisposeOf метод позволяет прямое обращение к деструктору объекта и позволяет использовать такой старый код вместе с ARC.

Здесь нужно отметить одно. Любой код, который использует или наследует от TComponent автоматически, становится устаревшим кодом в контексте правильного управления ARC, даже если вы пишете его сегодня.

Цитата из блога Allen Bauer Дайте стороне ARC

Так что же еще делает DisoseOf? Это очень распространено среди различных Рамки Delphi (включая VCL и FireMonkey), чтобы разместить активные код уведомления или списка управления внутри конструктора и деструктор класса. Собственная модель TComponent - это ключ пример такой конструкции. В этом случае существующий компонент каркасный дизайн опирается на многие виды деятельности, кроме простого "ресурса" управление "произойдет в деструкторе.

TComponent.Notification() является ключевым примером такой вещи. В этом случай, правильный способ" распоряжаться" компонентом - использовать DisposeOf. Производная TComponent обычно не является временным экземпляром, скорее более долгоживущий объект, который также окружен всей системой другие экземпляры компонентов, которые составляют такие вещи, как формы, фреймы и datamodules. В этом случае целесообразно использовать DisposeOf.

Как работает DisposeOf

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

Существуют три различных этапа, связанных с выпуском объекта как в компиляторах ARC, так и не в ARC Delphi.

  • вызов destructor Destroy методов цепочка
  • очистка объектов, управляемых полей - строк, интерфейсов, динамических массивов (в компиляторе ARC, который также включает ссылки на обычные объекты)
  • освобождение памяти объекта из кучи

Освобождение объекта с компиляторами, отличными от ARC

Component.Free → немедленное выполнение этапов 1 -> 2 -> 3

Освобождение объекта с помощью компиляторов ARC

  • Component.Free или Component := nil → уменьшает количество ссылок на объекты, а затем a) или b)

    • a), если счетчик ссылок на объекты равен 0 → немедленное выполнение этапов 1 -> 2 -> 3
    • b), если количество ссылок на объекты больше 0, ничего не происходит

  • Component.DisposeOf → немедленное выполнение этапа 1, этапы 2 и 3 будут выполнены позже, когда количество ссылок на объекты достигнет 0. DisposeOf не уменьшает количество ссылок на вызов.

Система уведомлений TComponent

Механизм

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

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

Механизм

Free Notification запускается в TComponent деструкторе и без DisposeOf и прямое выполнение деструктора, два компонента могут содержать сильные ссылки друг на друга, сохраняя себя живыми в течение всего срока службы приложения.

FFreeNotifies список, который содержит список компонентов, заинтересованных в уведомлении, объявляется как FFreeNotifies: TList<TComponent>, и он будет хранить сильную ссылку на любой зарегистрированный компонент.

Итак, если у вас есть TEdit и TPopupMenu в вашей форме и назначить это всплывающее меню для редактирования свойства PopupMenu, редактирование будет содержать сильную ссылку на всплывающее меню в поле FEditPopupMenu, а всплывающее меню будет сильная ссылка на редактирование в его списке FFreeNotifies. Если вы хотите выпустить любой из этих двух компонентов, вы должны называть DisposeOf на них или они просто будут продолжать существовать.

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

Следующий код в основном приведет к утечке обоих компонентов в ARC, потому что они будут содержать сильную ссылку друг на друга, а после завершения процедуры вы больше не будете иметь внешних ссылок, указывающих на один из этих компонентов. Однако, если вы замените Menu.Free на Menu.DisposeOf, вы вызовете механизм Free Notification и сломаете сильный ссылочный цикл.

procedure ComponentLeak;
var
  Edit: TEdit;
  Menu: TPopupMenu; 
begin
  Edit := TEdit.Create(nil);
  Menu := TPopupMenu.Create(nil);

  Edit.PopupMenu := Menu; // creating strong reference cycle

  Menu.Free; //  Menu will not be released because Edit holds strong reference to it
  Edit.Free; // Edit will not be released because Menu holds strong reference to it
end;

Ловушки DisposeOf

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

1. DisposeOf не уменьшает счетчик ссылок при вызове ссылки отчет QP RSP-14681

type
  TFoo = class(TObject)
  public
    a: TObject;
  end;

var
  foo: TFoo; 
  b: TObject;

procedure DoDispose;
var
  n: integer;
begin
  b := TObject.Create;
  foo := TFoo.Create;
  foo.a := b;
  foo.DisposeOf;
  n := b.RefCount; // foo is still alive at this point, also keeping b.RefCount at 2 instead of 1
end;

procedure DoFree;
var
  n: integer;
begin
  b := TObject.Create;
  foo := TFoo.Create;
  foo.a := b;
  foo.Free;
  n := b.RefCount; // b.RefCount is 1 here, as expected 
end;

2. DisposeOf не очищает ссылки на внутренние управляемые типы экземпляров отчет QP RSP-14682

type
  TFoo = class(TObject)
  public
    s: string;
    d: array of byte;
    o: TObject;
  end;

var
  foo1, foo2: TFoo;

procedure DoSomething;
var
  s: string;
begin
  foo1 := TFoo.Create;
  foo1.s := 'test';
  SetLength(foo1.d, 1);
  foo1.d[0] := 100;
  foo1.o := TObject.Create;
  foo2 := foo1;
  foo1.DisposeOf;
  foo1 := nil;
  s := IntToStr(foo2.o.RefCount) + ' ' + foo2.s + ' ' + IntToStr(foo2.d[0]); 
  // output: 1 test 100 - all inner managed references are still alive here, 
  // and will live until foo2 goes out of scope
end;

обходной путь

destructor TFoo.Destroy;
begin
  s := '';
  d := nil;
  o := nil;
  inherited;
end;

Комбинированный эффект вышеуказанных проблем может проявляться разными способами. Из-за хранения более выделенной памяти, чем необходимо, чтобы трудно поймать ошибки, вызванные неправильным, неожиданным количеством ссылок на содержащиеся ссылки на объекты и интерфейсы, не принадлежащие им.

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

3. DisposeOf не может использоваться для разрешения всех круговых ссылок

Последняя, ​​но не менее важная проблема с DisposeOf заключается в том, что она будет разбивать циклические ссылки только в том случае, если есть код в деструкторе, который разрешает их, как это делает система уведомлений TComponent.

Такие циклы, которые не обрабатываются деструктором, должны быть разбиты с использованием атрибутов [weak] и/или [unsafe] на одной из ссылок. Это также предпочтительная практика ARC.

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

Простым примером цикла, который не будет разбит на DisposeOf, является:

type
  TChild = class;

  TParent = class(TObject)
  public
    var Child: TChild;
  end;

  TChild = class(TObject)
  public
    var Parent: TParent;
    constructor Create(AParent: TParent);
  end;

constructor TChild.Create(AParent: TParent);
begin
  inherited Create;
  Parent := AParent;
end;

var
  p: TParent;
begin
  p := TParent.Create;
  p.Child := TChild.Create(p);
  p.DisposeOf;
  p := nil;
end;

Над кодом будет утечка как дочерних, так и родительских объектов. В сочетании с тем, что DisposeOf не очищает внутренние управляемые типы (включая строки), эти утечки могут быть огромными в зависимости от того, какие данные вы храните внутри. Единственный (правильный) способ разбить этот цикл - это изменить объявление класса TChild:

  TChild = class(TObject)
  public
    [weak] var Parent: TParent;
    constructor Create(AParent: TParent);
  end;

Ответ 2

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

Теперь вы можете использовать DisposeOf, чтобы заставить объект быть уничтоженным. Подробнее здесь: http://blogs.embarcadero.com/abauer/2013/06/14/38948

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

Ответ 3

Я пробовал все выше и не получил нигде. Моим последним вариантом был, возможно, тот, который приведет к цепочке комментариев с ненавистью, но единственное возможное решение, которое сработало в конце для меня.

У меня есть прокрутка и добавляются компоненты, которые я сделал сам (производный от TPanel) - в основном 50 за раз. Моим лучшим решением было следующее:

Перед созданием второго уровня элементов:

Scrollbox1.align := TAlignLayout.none;
Scrollbox1.width := 0;
Scrollbox1.position.y := 5000;
scrollbox1.visible := false;
scrollbox1.name := 'Garbage' + inttostr(garbageitemscount);
garbageitemscount := garbageitemscount + 1;

scrollbox1 := TScrollbox.create(nil);
scrollbox1.parent := ContainerPanel;
Scrollbox1.align := TAlignLayout.client;

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

Я не могу думать, что этот подход вообще возможен для приложений большего масштаба, но в конце концов подходит моим потребностям. Для тех, кто также не может понять, что делать. Component.DisposeOf - Crashed App Component.Free - никакого эффекта вообще

В действительности, согласно моему заявлению:

Component := TComponent.create(ScrollBox1);
Component.Parent := ScrollBox1;
showmessage(inttostr(ScrollBox1.ChildrenCount)); //<-- This says 0, even tho 50 Components are present