Cómo liberar un componente en Android/iOS
delphi components (3)
Creo dinámicamente un
TEdit
en un formulario en Android:
edit := TEdit.Create(Self);
Quiero liberarlo usando
edit.Free
, pero todavía está en forma.
Este código funciona bien en win32, pero falló en Android.
Lo mismo parece suceder no solo para TEdit sino para cualquier componente que use Android o iOS.
Respuesta corta
Hay dos reglas que se deben seguir al liberar cualquier objeto descendiente de
TComponent
en los compiladores Delphi ARC (actualmente Android e iOS):
-
el uso de
DisposeOf
es obligatorio independientemente de que el objeto tenga o no propietario -
en destructores o en casos donde la referencia no está fuera de alcance poco después de que se
DisposeOf
, la referencia de objeto también debe establecerse ennil
(explicación detallada en Pitfalls)
Puede ser atractivo tener el método
DisposeOfAndNil
, pero ARC lo hace mucho más complicado de lo que era con el antiguo método
FreeAndNil
y sugeriría usar la secuencia
DisposeOf - nil
simple para evitar problemas adicionales:
Component.DisposeOf;
Component := nil;
Si bien en muchos casos el código funcionará correctamente incluso si no se siguen las reglas anteriores, dicho código sería bastante frágil y podría ser fácilmente roto por otro código introducido en lugares aparentemente no relacionados.
DisposeOf en el contexto de la gestión de memoria ARC
DisposeOf
rompe el ARCO.
Viola la regla de oro de ARC.
Cualquier referencia de objeto puede ser una referencia de objeto válida o nula
e introduce una tercera referencia de objeto
"zombie" dispuesta en
estado.
Cualquier persona que intente comprender la administración de la memoria ARC debería considerar
DisposeOf
como una adición que solo resuelve problemas específicos del marco de Delphi y no un concepto que realmente pertenece al propio ARC.
¿Por qué existe DisposeOf en los compiladores ARC de Delphi?
TComponent
clase
TComponent
(y todos sus descendientes) se diseñó teniendo en cuenta la gestión manual de la memoria.
Utiliza un mecanismo de notificación que no es compatible con la administración de memoria ARC porque se basa en romper ciclos de referencia fuertes en el destructor.
Dado que
TComponent
es una de las clases base en las que confían los marcos de trabajo de Delphi, debe poder funcionar correctamente bajo la administración de memoria ARC.
Además
Free Notification
mecanismo de
Free Notification
, hay otros diseños similares en los marcos de Delphi adecuados para la gestión manual de la memoria porque dependen de romper ciclos de referencia fuertes en el destructor, pero esos diseños no son adecuados para ARC.
DisposeOf
método
DisposeOf
permite la llamada directa del destructor de objetos y permite que dicho
código heredado
juegue junto con ARC.
Una cosa debe tenerse en cuenta aquí.
Cualquier código que use o herede de
TComponent
se convierte automáticamente en
código heredado
en el contexto de una gestión ARC adecuada, incluso si lo escribe hoy.
Cita del blog de Allen Bauer Ceda al lado del ARC
Entonces, ¿qué más resuelve DisoseOf? Es muy común entre varios marcos de Delphi (VCL y FireMonkey incluidos), colocar notificaciones activas o códigos de administración de listas dentro del constructor y destructor de una clase. El modelo propietario / propietario de TComponent es un ejemplo clave de dicho diseño. En este caso, el diseño del marco de componentes existente se basa en muchas actividades distintas de la simple "gestión de recursos" que tiene lugar en el destructor.
TComponent.Notification () es un ejemplo clave de tal cosa. En este caso, la forma correcta de "desechar" un componente es utilizar DisposeOf. Una derivada de TComponent no suele ser una instancia transitoria, sino que es un objeto de vida más larga que también está rodeado por un sistema completo de otras instancias de componentes que componen elementos como formularios, marcos y módulos de datos. En este caso, usar DisposeOf es apropiado.
Cómo funciona DisposeOf
Para comprender mejor qué sucede exactamente cuando se llama a
DisposeOf
, es necesario saber cómo funciona el proceso de destrucción de objetos de Delphi.
Hay tres etapas distintas involucradas en la liberación de objetos en compiladores Delphi ARC y no ARC
-
llamando al
destructor Destroy
cadena de métodos - limpieza de campos administrados por objetos: cadenas, interfaces, matrices dinámicas (en el compilador ARC que también incluye referencias a objetos simples)
- liberar memoria del objeto del montón
Liberar objeto con compiladores que no son ARC
Component.Free
-> ejecución inmediata de las etapas
1 -> 2 -> 3
Liberando objeto con compiladores ARC
-
Component.Free
oComponent := nil
-> disminuye el recuento de referencia de objeto seguido de a) o b)-
a)
si el recuento de referencias de objeto es 0 -> ejecución inmediata de las etapas
1 -> 2 -> 3
-
b)
si el recuento de referencia de objeto es mayor que 0, no sucede nada más
-
a)
si el recuento de referencias de objeto es 0 -> ejecución inmediata de las etapas
-
Component.DisposeOf
-> la ejecución inmediata de la etapa1
, las etapas2
y3
se ejecutarán más tarde cuando el recuento de referencias de objetos alcance 0.DisposeOf
no disminuye el recuento de referencias de la llamada de referencia.
Sistema de notificación de TComponent
Free Notification
mecanismo de notificación
Free Notification
TComponent
Free Notification
a los componentes registrados que se está liberando una instancia de componente en particular.
Los componentes notificados pueden manejar esa notificación dentro del método de
Notification
virtual y asegurarse de que borren todas las referencias que puedan tener sobre el componente que se está destruyendo.
En los compiladores que no son ARC, ese mecanismo garantiza que no termines con punteros colgantes que apuntan a objetos no válidos y, en los compiladores ARC, borrar las referencias al componente destructor disminuirá su recuento de referencias y romperá ciclos de referencia fuertes.
Free Notification
mecanismo de
Free Notification
se está
TComponent
en el destructor
TComponent
y sin
DisposeOf
y la ejecución directa del destructor, dos componentes podrían tener fuertes referencias entre sí y mantenerse vivos durante toda la vida útil de la aplicación.
FFreeNotifies
lista
FFreeNotifies
que contiene la lista de componentes interesados en la notificación se declara como
FFreeNotifies: TList<TComponent>
y almacenará una fuerte referencia a cualquier componente registrado.
Entonces, por ejemplo, si tiene
TEdit
y
TPopupMenu
en su formulario y asigna ese menú emergente a la propiedad
PopupMenu
edición, edit tendrá una fuerte referencia al menú emergente en su campo
FEditPopupMenu
, y el menú emergente tendrá una fuerte referencia para editar en su lista
FFreeNotifies
.
Si desea liberar cualquiera de esos dos componentes, debe llamar a
DisposeOf
en ellos o simplemente continuarán existiendo.
Si bien puede intentar rastrear esas conexiones manualmente y romper ciclos de referencia fuertes antes de liberar cualquiera de esos objetos que pueden no ser tan fáciles de hacer en la práctica.
El siguiente código básicamente filtrará ambos componentes bajo ARC porque mantendrán una fuerte referencia entre sí, y después de que finalice el procedimiento, ya no tendrá referencias externas que apunten a ninguno de esos componentes.
Sin embargo, si reemplaza
Menu.Free
con
Menu.DisposeOf
, activará el mecanismo de
Free Notification
y romperá el ciclo de referencia fuerte.
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;
Las trampas de la eliminación
Además de romper ARC, eso es malo por sí solo, porque cuando lo rompes no tienes mucho uso de él, también hay dos problemas principales sobre cómo se implementa
DisposeOf
que los desarrolladores deben tener en cuenta.
1.
DisposeOf
no disminuye el recuento de referencias en el
informe QP de
referencia de llamada
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
no limpia la instancia interna tipos gestionados referencias
QP informe 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;
solución alternativa
destructor TFoo.Destroy;
begin
s := '''';
d := nil;
o := nil;
inherited;
end;
El efecto combinado de los problemas anteriores puede manifestarse de diferentes maneras. Desde mantener más memoria asignada de la necesaria hasta errores difíciles de detectar que son causados por un recuento de referencias incorrecto e inesperado de referencias de interfaz y objetos no propios contenidos.
Dado que
DisposeOf
no disminuye el recuento de referencias de referencia de llamada, es importante
nil
dicha referencia en los destructores; de lo contrario, las jerarquías de objetos completos pueden permanecer activas mucho más tiempo del necesario y, en algunos casos, incluso durante toda la vida de la aplicación.
3.
DisposeOf
no se puede usar para resolver todas las referencias circulares
Por último, pero no menos importante, el problema con
DisposeOf
es que romperá las referencias circulares solo si hay un código en el destructor que las resuelva, como lo
TComponent
sistema de notificación
TComponent
.
Dichos ciclos que no son manejados por destructor deben romperse usando atributos
[weak]
y / o
[unsafe]
en una de las referencias.
Esa también es la práctica preferida de ARC.
DisposeOf
no debe usarse como una solución rápida para romper
todos los
ciclos de referencia (para los que nunca fue diseñado) porque no funcionará y abusar de él puede ocasionar pérdidas de memoria difíciles de rastrear.
Un ejemplo simple de ciclo que no se romperá con
DisposeOf
es:
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;
El código anterior filtrará las instancias de objeto secundario y primario.
Combinado con el hecho de que
DisposeOf
no borra los tipos administrados internos (incluidas las cadenas), esas filtraciones pueden ser enormes dependiendo del tipo de datos que esté almacenando en su interior.
La única forma (adecuada) de romper ese ciclo es cambiando la declaración de clase
TChild
:
TChild = class(TObject)
public
[weak] var Parent: TParent;
constructor Create(AParent: TParent);
end;
En las plataformas móviles, la vida útil se gestiona utilizando ARC. Los objetos solo se destruyen cuando no quedan referencias al objeto restante. Su objeto tiene referencias a él, específicamente de su padre.
Ahora podría usar
DisposeOf
para forzar la
DisposeOf
del objeto.
Más detalles aquí:
http://blogs.embarcadero.com/abauer/2013/06/14/38948
Sin embargo, sospecho que una mejor solución sería eliminar las referencias al objeto. Retirarlo de su contenedor. Por ejemplo, configurando su padre para que sea nulo.
He intentado todo lo anterior y no llegué a ninguna parte ... Mi última opción fue posiblemente una que me lleve a una serie de comentarios de odio, pero la única solución posible que funcionó al final para mí ...
Tengo un cuadro de desplazamiento y agrego componentes que hice yo mismo (derivados de TPanel), principalmente 50 a la vez. Mi mejor solución fue esta:
Antes de crear el segundo nivel de elementos:
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;
Al crear los componentes, su padre se configura de nuevo en Scrollbox1. Esto da la ilusión de que los elementos se han ido cuando, de hecho, simplemente se mueven fuera de la pantalla hasta que la aplicación se cierra y Android los libera.
No puedo pensar que este enfoque sea factible para aplicaciones de mayor escala, pero al final satisfizo mis necesidades. Para aquellos que tampoco saben qué hacer. Component.DisposeOf - Componente de aplicación bloqueada. Gratis - Sin efecto alguno
De hecho, según mi solicitud:
Component := TComponent.create(ScrollBox1);
Component.Parent := ScrollBox1;
showmessage(inttostr(ScrollBox1.ChildrenCount)); //<-- This says 0, even tho 50 Components are present