Utilizando interfaces em classes com herança diferente de TInterfacedObject

TInterfacedObject é realmente últil quando precisa-se implementar uma classe com uma interface. Isso porque ele cuida de toda a operação da contagem de referências automaticamente (ARC – Automatic Reference Count). Graças ao ARC, o desenvolvedor não precisa se preocupar em desalocar a interface da memória, porque isso será feito automaticamente pelo compilador.

Porém, existem casos onde a classe que precisa implementar a interface já herda de outra classe, diferente de TInterfacedObject. O que fazer?

Se simplesmente adicionarmos as interfaces na declaração da classe, obteremos a mensagem de erro na compilação:

[dcc32 Error] Unit2.pas(22): E2291 Missing implementation of interface method IInterface.QueryInterface
[dcc32 Error] Unit2.pas(22): E2291 Missing implementation of interface method IInterface._AddRef
[dcc32 Error] Unit2.pas(22): E2291 Missing implementation of interface method IInterface._Release

Isso já nos dá a dica necessária para implementarmos nossa própria versão do TInterfacedObject na nossa nova classe.

Todas as vezes que criamos um novo objeto interfaceado, o compilador automaticamente chama o método _AddRef. Todas as vezes que o compilador entende que o objeto não será mais utilizado (por uma chamada de Objeto := nil, por exemplo), o compilador automaticamente chama o método _Release. Com esses dois métodos, o compilador consegue gerenciar a quantidade de referências utilizadas dessa interface e saber quando o objeto será destruído da memória. Existe ainda o método QueryInterface, que é utilizado todas as vezes onde é necessário saber se o método implementa uma interface (lembra a declaração da GUID na interface?). Uma vez implementado esses 3 métodos, já é possível utilizar o novo objeto.

Vamos entender individualmente cada um deles, mas antes, vamos falar um pouco sobre o ARC:

ARC

ARC ou Automatic Reference Counting é uma das diferentes formas de gerenciamento de memória. Ela é baseada na contagem de referências dentro do programa. Toda vez que um novo objeto (ou interface, no caso do Delphi) é instanciada, uma nova referência é adicionada. Sempre que o compilador entende que o objeto não será mais utilizado, seja por explicitamente trocar a referência do objeto (Obj := nil, por exemplo), ou pelo próprio término do escopo (o término de uma função, onde o objeto foi declarado), a referência é decrementada. Assim, quando a referência cai a zero, significa que a instância pode ser destruída da memória, porque não será mais utilizada.

ARC é utilizado em linguagens como Objective-C (e Swift). O Delphi o implementou como gerenciador das interfaces. Ao contrário do garbage colector, o ARC não precisa de um processo em background para gerenciar a limpeza dos objetos da memória, porque sabe, no momento do último release, quando o objeto pode ser destruído da memória.

Existe uma grande discussão entre as vantagens e desvantagens de se utilizar ARC ou Garbage Collector. Cada uma possui as suas características e suas vantagens. Cabe a cada desenvolvedor se identificar com cada uma.

_AddRef

Sempre que um novo objeto é instanciado, uma nova referencia à interface é incrementada, pela chamada de _AddRef. Assim, o compilador sabe a quantidade correta, de quantas instâncias ainda estão sendo utilizadas, antes de destruir da memória a referência da interface.

Para implementar o método _AddRef:

Aqui basicamente utilizamos AtomicIncrement para incrementarmos a variável FRefCount. AtomicIncrement é “thread-safe” e “platform-safe”, se assim podemos dizer. Isso significa que o funcionamento independe da utilização de threads ou se o software é VCL ou Firemonkey (rodando em Android, iOS, etc).

_Release

Sempre que o compilador entende que uma instância de uma interface perdeu sua referência, seja explicitamente pela chamada de Interface := nil, ou pelo término do escopo, onde a interface não será mais necessária, o compilador chama automaticamente o método _Release. Quando a quantidade chega a zero, o compilador sabe que pode desalocar da memória a interface, porque ela não será mais necessária. Assim, o desenvolvedor não precisa se preocupar com retira-la manualmente, através da chamada de Free ou outra semelhante.

Para implementar o método _Release:

Aqui também, decrementamos a variável através de AtomicDecrement.

QueryInterface

QueryInterface é mais complexa. Ela determina se o objeto implementa uma determinada interface.

A implementação de QueryInterface é:

Feito isso, qualquer objeto estará apto a funcionar como interfaces.

Exemplo completo

Abaixo encotra-se um exemplo de como funciona a interface. Para que você consiga certificar-se que realmente funcionou, habilite ReportMemoryLeaksOnShutdown para True.

Projeto

 

Unit

Conclusão

Você pode obter um detalhamento maior através da própria implementação de TInterfacedObject em System.pas.

Conforme o anúncio do RoadMap 2017/2018 feito pela embarcadero em https://community.embarcadero.com/article/news/16519-rad-studio-roadmap-may-2018, eles estão estudando utilizar ARC nas próprias aplicações Windows. Hoje, para aplicações Firemonkeys, dependendo da plataforma utilizada, todos os objetos são gerenciados pelo ARC, e não apenas as interfaces. Espero que isso concretize-se e nos dê a facilidade de traballhar com mais essa tecnologia em nossas aplicações legadas.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *