Garbage Collector no Delphi – Parte I
Olá pessoal, depois de algum tempo sem postar venho com uma novidade para vocês, para alguns isto pode não ser novidade, mas tenho certeza que para outros é, pelo menos para mim foi novidade, programo em Delphi desde 2005 mais ou menos e fiquei sabendo que o Delphi tinha Garbage Collector só a dias atrás então resolvi criar uma implementação de exemplo.
Neste primeiro exemplo vou trabalhar apenas com objetos que herdam de uma classe Super Classe.
A primeira implementação é da Super Classe na qual todas as outras classes irá herdá-la, a classe é bem simples ela tem uma interface do IModel que é do mesmo tipo da IUnknown que será responsável por dizer que o objeto será liberado automaticamente e a classe TModel é quem irá herdar TInterfacedObject e a inteface IUnknown.
unit Model; interface type IModel = type IUnknown; TModel = class(TInterfacedObject, IModel) end; implementation end.
A segunda implementação é da Sub Classe, ela irá conter uma interface que irá conter todos os métodos necessários na classe principal e uma classe que irá herdar a Super Classe e implementar a classe que contém os métodos que serão usados na classe.
Note que na frente da declaração do método Destroy existe um override pois o mesmo está declarado como virtual na classe TObject o que indica que o mesmo pode ser sobrescrito na Sub Classe.
unit ModelPessoa;
interface
uses
Dialogs, Model;
type
IModelPessoa = interface
procedure Teste();
end;
TModelPessoa = class(TModel, IModelPessoa)
public
{ Public declarations }
constructor Create();
destructor Destroy(); override;
procedure Teste();
end;
implementation
{ TModelPessoa }
constructor TModelPessoa.Create();
begin
ShowMessage('Create');
end;
destructor TModelPessoa.Destroy();
begin
ShowMessage('Destroy');
inherited Destroy();
end;
procedure TModelPessoa.Teste();
begin
ShowMessage('Teste');
end;
end.
Pronto agora é só usar mos, primeiro temos que criar uma variável do tipo da interface da Sub Classe, pois é que o coletor ficará verificando e quando a mesma sair do contexto será capturado automaticamente pelo Garbage Collector. Depois vamos instanciar esta variável pela classe concreta que implementa a nossa interface para podermos usar o objeto, note que ao instanciar o objeto irá aparecer a mensagem “Create”, apos executar pelo método Teste() irá aparecer a mensagem “Teste” e por final quando terminar o método principal na qual foi declarada a variável do tipo da interface da Sub Classe irá aparecer a mensagem “Destroy” que indicará que a nossa variável foi liberada da memória pelo Garbage Collector do Delphi.
var objPessoa: IModelPessoa; begin objPessoa := TModelPessoa.Create(); objPessoa.Teste(); end;
Este exemplo foi feito em Delphi XE, caso queria mais uma forma de verificar se a variável foi realmente liberada da memória o Delphi XE tem a propriedade ReportMemoryLeaksOnShutdown que mostra os objetos que ainda estão na memória.
Segue abaixo uma forma de usar esta propriedade.
program Estudo11;
uses
Forms,
Unit8 in 'Unit8.pas' {Form8},
Model in 'Model.pas',
ModelPessoa in 'ModelPessoa.pas';
{$R *.res}
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm8, Form8);
Application.Run;
end.
Espero que este exemplo seja útil a vocês de alguma forma, até a próxima.
Brow, até onde me consta, isso sempre existiu, desde os tempos do Delphi 3 se não me falhe a memória.
Olá Rubem, agradeço o comentário como tinha escrito no inicio do post realmente para algumas pessoas não será novidade nenhuma fiz este post pois em todos os lugares que passei eu ouvi a frase que o Delphi não tinha Garbafe Collector e resolvi verificar se era verdade até que descobri uma forma de implementar e decidir compartilhar com a comunidade. Até mais!
Apesar de já ser conhecido o procedimento, o uso e a abordagem foram muito prática e inteligente.
Parabens e obrigado por dividir a experiencia conosco.
Obrigado Alex pelo comentário, é sempre gratificante quando uma dica ajuda alguém da comunidade.
Junior, só uma informação: Reference counting não é o mesmo que garbage collector.
Na verdade, no Delphi, reference counting é usado automaticamente sempre que atribuimos um objeto a uma interface, ou esta interface a uma outra.
Sempre que se tem uma nova referencia a um objeto que implementa uma interface o contador de referencias incrementa.
Ele decrementa quando uma referência se perde ou saímos do escopo onde ela é usada (por exemplo o término de um método onde a interface é usada).
Isso é controlado pelos métodos _AddRef e _Release de TInterfacedObject. Você pode ver o source de TInterfacedObject como ele funciona e até criar uma classe sua igual a TInterfacedObject. Fazendo isso você não precisa descender de TInterfacedObject.
Outras classes que suportam interfaces, como TInterfacedPersistent, também fazem isso. Mas uma form, embora implemente _AddRef e _Release, implementa de maneira diferente, e pro isso precisa ser liberada com free. (http://edn.embarcadero.com/article/28217)
De qualquer forma, ao contrário de outras linguagens, no Delphi interfaces não podem ser misturadas com objetos pelo seguinte fato: se você atribuir a um objeto uma interface, e este objeto ficar fora do escopo local de um método, ou ficar dentro de um contêiner qualquer, como outro objeto ou uma list, e se por um acaso sairmos do escopo onde estava a interface original, isto fará com que o contador de referências diminua de 1 para 0, destruindo o objeto prematuramente, mesmo quando precisamos usar depois. Teremos referências inválidas.
Um Garbage Collector real nunca destroi um objeto prematuramente, mesmo se fizermos “salada” com interfaces.
Olá Vitor,
Obrigado pela sua visita. E obrigado pelo seu comentário, realmente não sabia destas informações.
A parte 2 desse post ficou boa. Chegou um pouco mais perto de um GC, mas já tentou fazer isso?
var
SafeGuard: ISafeGuard;
label1: TLabel;
i : integer;
begin
label1 := TLabel.New(SafeGuard);
label1.Caption:= ‘aaaa’;
end;
Vai dar exception na hora de atribuir o valor ao caption. Teste com qualquer outra classe feita por você, como a TModelPessoa, por exemplo, coloque Shomessages ou breakpoints no constructor e no destructor. Você verá que o constructor e o destructor nunca são executados. Isso porque na function Guard você está fazendo TObject.Create. Todos os objetos criados com New são do tipo TObject, onde você está fazendo um casting forçado para o tipo genérico T.
Gostei muito desse assunto, se quiser eu dou uma revisada no seu código e reposto no meu blog. Vamos continuar conversando sobre isso.
Achei uns artigos interessantes sobre GC no Delphi. O Delphi foi escrito de maneira que o memory manager possa ser facilmente substituido, e já existe implementação de GC para ele, mas o Memory Manager usado por esse GC é um pouco mais lento do que o FastMM usado pelo Delphi. Além disso, o report de memoryleaks no final do programa, com ReportMemoryLeaksOnShutdown := true, só é possível graças ao FastMM. Se trocarmos o FastMM pelo GC o ReportMemoryLeaksOnShutdown := true parará de funcionar. Com isso não podemos verificar se o GC está realmente funcionando e se não houveram leaks. Felizmente o GC em questão possui os próprios mecanismos para detecção de leaks, vale a pena conferir.
http://codecentral.embarcadero.com/Item/21646 –> baseado no GC de c/c++ abaixo
http://www.hpl.hp.com/personal/Hans_Boehm/gc/
http://edn.embarcadero.com/article/28217
um Abraço!
Olá Vitor,
Obrigado mais uma vez pela visita e comentário, vou fazer os testes que você sugeriu. Fique à-vontade para refatorar o código tenho certeza que será de grande utilidade para todos da comunidade.
Assim que refatorar e publicar no seu blog, por favor, me avise para que eu possa dar uma olhada estou ansioso para ver os resultados.