Cómo liberar un componente en Android / iOS

Creo dinámicamente un TEdit en un formulario en Android:

 edit := TEdit.Create(Self); 

Quiero liberarlo usando edit.Free , pero solo sigue en forma.

Este código funciona bien en win32, pero falló en Android.

Lo mismo parece ocurrir no solo con TEdit sino también con cualquier componente que use Android o iOS.

Respuesta corta

Hay dos reglas que se deben seguir al liberar cualquier objeto descendente TComponent bajo los comstackdores Delphi ARC (actualmente Android e iOS):

  • usar DisposeOf es obligatorio independientemente del objeto que tenga el propietario o no
  • en destructores o en casos donde la referencia no está fuera de scope poco después de DisposeOf , la referencia del objeto también debe establecerse en nil (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 una DisposeOf - nil simple DisposeOf - nil 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 se podría romper fácilmente con otro código introducido en lugares aparentemente no relacionados.

DisposeOf en el contexto de la gestión de la memoria ARC

DisposeOf rompe ARC. 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” de estado .

Cualquiera que intente comprender la administración de la memoria ARC debería considerar DisposeOf como una adición que solo resuelve los problemas del marco específico de Delphi y no el concepto que realmente pertenece a ARC.

¿Por qué DisposeOf existe en los comstackdores Delphi ARC?

TComponent clase TComponent (y todos sus descendientes) se diseñaron teniendo en cuenta la administración manual de la memoria. Utiliza un mecanismo de notificación que no es compatible con la gestión de memoria ARC porque se basa en romper ciclos de referencia fuertes en destructor. Como TComponent es una de las clases base en las que se basan los frameworks Delphi, debe poder funcionar adecuadamente bajo la administración de memoria ARC.

Además Free Notification mecanismo de Free Notification , existen otros diseños similares en los marcos Delphi adecuados para la gestión manual de la memoria porque se basan en romper los fuertes ciclos de referencia en el destructor, pero esos diseños no son adecuados para ARC.

DisposeOf método DisposeOf habilita la llamada directa del destructor de objeto y permite que dicho código heredado se reproduzca junto con ARC.

Una cosa debe ser notada aquí. Cualquier código que utilice o herede de TComponent se convertirá 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 Ceder al lado de ARC

Entonces, ¿qué más resuelve DisoseOf? Es muy común entre varios frameworks Delphi (VCL y FireMonkey incluidos), colocar notificaciones activas o códigos de administración de listas dentro del constructor y el 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” para que suceda en el destructor.

TComponent.Notification () es un ejemplo clave de tal cosa. En este caso, la forma correcta de “eliminar” un componente es usar DisposeOf. Una derivada de TComponent no suele ser una instancia transitoria, sino que es un objeto de larga duración que también está rodeado por un sistema completo de otras instancias de componentes que conforman elementos tales como formularios, marcos y módulos de datos. En este caso, use DisposeOf es apropiado.

Cómo funciona DisposeOf

Para tener una mejor comprensión de qué ocurre exactamente cuando se llama DisposeOf , es necesario saber cómo funciona el proceso de destrucción de objetos Delphi.

Hay tres etapas distintas involucradas en la liberación de objetos en comstackdores Delphi ARC y no ARC

  1. calling destructor Destroy methods chain
  2. limpiar campos administrados por objetos: cadenas, interfaces, matrices dinámicas (bajo el comstackdor ARC que también incluye referencias de objetos simples)
  3. liberando memoria de objetos del montón

Liberación de objetos con comstackdores que no son ARC

Component.Free -> ejecución inmediata de las etapas 1 -> 2 -> 3

Liberación de objetos con comstackdores ARC

  • Component.Free o Component := nil -> disminuye el recuento de referencia de objeto seguido por a) ob)

    • a) si el recuento de referencia del objeto es 0 -> ejecución inmediata de las etapas 1 -> 2 -> 3
    • b) si el recuento de referencia del objeto es mayor que 0, no sucede nada más

  • Component.DisposeOf -> ejecución inmediata de la etapa 1 , las etapas 2 y 3 se ejecutarán más tarde cuando el recuento de referencia de objetos llegue a 0. DisposeOf no disminuye el recuento de referencias de la referencia de llamada.

Sistema de notificación TComponent

Free Notification mecanismo de notificación Free Notification TComponent Free Notification a los componentes registrados que se está liberando una instancia de componente 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 destruye.

Según los comstackdores que no son ARC, ese mecanismo garantiza que no termines con punteros colgantes que apuntan a objetos liberados no válidos y, de acuerdo con los comstackdores ARC, borrar las referencias al componente destructor disminuirá su recuento de referencias y romperá los fuertes ciclos de referencia.

Free Notification mecanismo de Free Notification se activa en el destructor TComponent y sin DisposeOf y la ejecución directa del destructor, dos componentes podrían contener fuertes referencias entre sí, manteniéndose con vida 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 y almacenará una referencia fuerte a cualquier componente registrado.

Entonces, por ejemplo, si tiene TEdit y TPopupMenu en su formulario y asigna ese menú emergente a la propiedad de editar PopupMenu , la edición 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 alguno de esos dos componentes, debe llamar a DisposeOf o simplemente continuarán existiendo.

Si bien puede intentar hacer un seguimiento de esas conexiones manualmente y romper ciclos de referencia fuertes antes de lanzar cualquiera de esos objetos que pueden no ser tan fáciles de hacer en la práctica.

El código siguiente básicamente filtrará ambos componentes en ARC porque mantendrán una fuerte referencia el uno al otro y, una vez finalizado 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 con la implementación de DisposeOf que los desarrolladores deberían tener en cuenta.

1. DisposeOf no disminuye el recuento de referencia 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 las referencias internas de los tipos gestionados de la instancia Informe de 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; 

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. De mantener más memoria asignada de la necesaria a difícil de detectar errores causados ​​por recuento de referencias incorrectas e inesperadas de referencias de objeto y de interfaz no de propiedad.

Como DisposeOf no disminuye el recuento de referencia de la referencia de llamada, es importante nil dicha referencia en destructores; de lo contrario, las jerarquías de objetos enteros pueden permanecer activos mucho más tiempo de lo necesario y, en algunos casos, incluso durante toda la vida útil 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 resuelve, como lo TComponent sistema de notificación TComponent .

Tales ciclos que no son manejados por destructor se deben romper usando atributos [weak] y / o [unsafe] en una de las referencias. Esa también es la práctica preferida de ARC.

DisposeOf no se debe usar como una solución rápida para romper todos los ciclos de referencia (para los que nunca se diseñó) porque no funcionará y abusar de él puede provocar un seguimiento difícil de las memory leaks.

Un ejemplo simple de ciclo que no será roto por 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á instancias de objetos secundarios y secundarios. Combinado con el hecho de que DisposeOf no DisposeOf tipos administrados internos (incluidas las cadenas), esas filtraciones pueden ser enormes dependiendo del tipo de datos que almacene dentro. La única forma (adecuada) de romper ese ciclo es cambiando la statement de la 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 hay 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. Eliminarlo de su contenedor. Por ejemplo, estableciendo su padre para ser nil.

Lo intenté todo arriba y no llegué a ninguna parte. Mi última opción posiblemente fue 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 (derivado de TPanel), en su mayoría 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 elemento principal se establece en Scrollbox1 nuevamente. Esto da la ilusión de que los elementos se han ido cuando, de hecho, se han movido de la pantalla hasta que se cierra la aplicación y luego son liberados por Android.

No puedo pensar que este enfoque sea del todo factible para aplicaciones de mayor escala, pero que al final se adapte a mis necesidades. Para aquellos que tampoco saben qué hacer. Component.DisposeOf – Crashed App Component.Free – Sin efecto en absoluto

De hecho, según mi aplicación:

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