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.

Respuestas a la pregunta(4)

Su respuesta a la pregunta