android ios delphi components firemonkey

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 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 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

  1. llamando al destructor Destroy cadena de métodos
  2. limpieza de campos administrados por objetos: cadenas, interfaces, matrices dinámicas (en el compilador ARC que también incluye referencias a objetos simples)
  3. 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 o Component := 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

  • Component.DisposeOf -> la ejecución inmediata de la etapa 1 , las etapas 2 y 3 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