Manejo de referencias circulares fuertes en Delphi
Obtuve dos clases (en mi ejemplo TObject1 y TObject2) que se conocen entre sí a través de interfaces (IObject1, IObject2). Como probablemente sepa en Delphi, esto provocará una pérdida de memoria ya que ambos contadores de referencia siempre se mantendrán por encima de cero. La solución habitual es declarar una referencia como débil. Esto funciona en la mayoría de los casos porque generalmente sabe cuál se destruirá primero o no necesariamente necesita el objeto detrás de la referencia débil una vez que se destruye.
Dicho esto, intenté resolver el problema de manera que ambos objetos permanezcan vivos hasta que ya no se haga referencia a ambos: (se requiere Delphi 10.1 ya que uso el atributo [inseguro])
program Project14;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
IObject2 = interface;
IObject1 = interface
['{F68D7631-4838-4E15-871A-BD2EAF16CC49}']
function GetObject2: IObject2;
end;
IObject2 = interface
['{98EB60DA-646D-4ECF-B5A7-6A27B3106689}']
end;
TObject1 = class(TInterfacedObject, IObject1)
[unsafe] FObj2: IObject2;
constructor Create;
destructor Destroy; override;
function GetObject2: IObject2;
end;
TObject2 = class(TContainedObject, IObject2)
[unsafe] FObj1: IObject1;
constructor Create(aObj1: IObject1);
destructor Destroy; override;
end;
constructor TObject1.Create;
begin
FObj2 := TObject2.Create(Self);
end;
destructor TObject1.Destroy;
begin
TContainedObject(FObj2).Free;
inherited Destroy;
end;
function TObject1.GetObject2: IObject2;
begin
Result := FObj2;
end;
constructor TObject2.Create(aObj1: IObject1);
begin
inherited Create(aObj1);
FObj1 := aObj1;
end;
destructor TObject2.Destroy;
begin
inherited Destroy;
end;
function Test1: IObject1;
var
x: IObject2;
begin
Result := TObject1.Create;
x := Result.GetObject2;
end;
function Test2: IObject2;
var
x: IObject1;
begin
x := TObject1.Create;
Result := x.GetObject2;
end;
var
o1: IObject1;
o2: IObject2;
begin
try
o1 := Test1();
o2 := Test2();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Esto funciona tal como está ... la función Test1 y Test2 crean una instancia de TObject1 y TObject2 que se refieren entre sí y todas las instancias se destruyen una vez que o1 y o2 quedan fuera del alcance. La solución se basa en TContainedObject que reenvía el recuento al "controlador" (TObject1 en este caso).
Ahora sé que esta solución tiene fallas, y aquí es donde comienzan mis preguntas:
"TContainedObject (FObj2) .Free;" huele un poco, pero no tengo una solución mejor ya que necesito usar una interfaz para hacer referencia a TObject2 (el código productivo contiene algunas herencias en este extremo). ¿Alguna idea para limpiarlo?olvida fácilmente declarar todas las referencias entre las 2 clases como débiles y ...un problema similar comienza a surgir con más clases: tener TObject3 al que hace referencia una y hace referencia a la otra: pérdida de memoria. Podría manejarlo dejando que descienda también de TContainedObject, pero con el código heredado esto podría no ser una tarea fácil.Tengo la sensación de que esta solución no se puede aplicar universalmente y espero una que sí pueda, o tal vez una respuesta que describa por qué es tan difícil o incluso imposible tener una solución 100% fácil de usar para manejar tales situaciones. En mi opinión, no puede ser demasiado complicado tener una cantidad finita de objetos que se destruyen entre sí una vez que no se hace referencia a ellos fuera de su dominio sin tener que pensar cuidadosamente en cada referencia dentro de este dominio.