ARC - tedy automatické počítání referencí a uvolňování není principiální novinkou v XE4. Už od Delphi 2 podporuje stejný mechanismus pro řetězce (resp. pro dlouhé stringy). U řetězců je navíc implementován COW (Copy-On-Write) mechanismus.
COW znamená, že můžete řetězec libovolně přiřazovat a předávat a Delphi jen zvětšuje a zmenšuje počítadlo referencí. Pokud řetězec zmizí z viditelnosti, je snížen počet odkazů a při nulovém počtu je řetězec uvolněn (tedy přesněji paměť je v memory manageru označena jako dostupná a je jí možno použít znovu jinde). V případě změny řetězce je vytvořen nový řetězec (to je to COW, kopírování při zápisu). Toto je velmi efektivní a uživatelsky příjemné chování. Více jsem psal v článku o uvolňování řetězců a v článku o řetězcích v Delphi.
Později přicházejí rozhraní (interface) a Delphi opět počítá reference.
Nyní se to rozšiřuje pro objekty - logické pokračování. Týká se to jen ARM, nikoliv jiných kompilátorů.
Základní třída je rozšířena pro podporu ARC.
TObject = class
procedure DisposeOf; {$IFNDEF AUTOREFCOUNT} inline; {$ENDIF}
..
{$IFDEF AUTOREFCOUNT}
function __ObjAddRef: Integer; virtual;
function __ObjRelease: Integer; virtual;
{$ENDIF}
….
{$IFDEF AUTOREFCOUNT}
protected
{$ENDIF}
destructor Destroy; virtual;
…..
{$IFDEF AUTOREFCOUNT}
private const
objDestroyingFlag = Integer($80000000);
objDisposedFlag = Integer($40000000);
protected
[Volatile] FRefCount: Integer;
public
property RefCount: Integer read FRefCount;
{$ENDIF}
property Disposed: Boolean read GetDisposed;
end;
Na rozdíl od GC (garbage collection) v .NET je ARC deterministické a zánik objektů může být plně pod kontrolou programátora nebo to prostě nechat na ARC.
class procedure TMySimpleClass.CreateOnly;
var
MyObj: TMySimpleClass;
begin
MyObj := TMySimpleClass.Create;
MyObj.DoSomething;
end;
V tomto případě na konci metody je objekt uvolněn - lokální proměnná ztrácí viditelnost. Ještě upozorním, že za pomocí {$IFDEF AUTOREFCOUNT} lze testovat kompilátor, ale je to zbytečné. Proč?
Soustřeďme se nyní jen na ARC. Pokud na některém objektu nastavíme obj := nil je sníženo počítadlo referencí (stejně jako když zmízí na konci funkce). Při nulovém počtu je objekt uvolněn.
Aby byla zajištěna kompatibilita s klasickými Delphi, je volání Free nahrazováno kompilátorem jako přiřazením nil. Tj. pokud Váš kód používá Free (nebo FreeAndNil) bude bez problému fungovat jak s ARC tak bez ARC (až na drobnou výjimku).
procedure TObject.Free;
begin
// under ARC, this method isn't actually called since the compiler translates
// the call to be a mere nil assignment to the instance variable, which then calls _InstClear
{$IFNDEF AUTOREFCOUNT}
if Self <> nil then
Destroy;
{$ENDIF}
end;
Takže v ARC není třeba používat Free nebo FreeAndNil, ale pokud chcete kompatibilitu s normálním Delphi tak to nutné je.
class procedure TMySimpleClass.CreateOnly;
var
MyObj, MyObj2: TMySimpleClass;
begin
MyObj := TMySimpleClass.Create;
MyObj2 := MyObj; // +1 reference
MyObj := nil; // -1 reference
MyObj2.DoSomething; // objekt zije
MyObj2 := nil; //uz ne
end;
V určitých případech (jako je uvolnění zdrojů typu soubor) je vhodné umožnit jejich explicitní uvolnění i když nemusí být všechny reference na objekt již neplatné. Pro tyto případy je přidána metoda
MyObject.DisposeOf;
Tato metoda explicitně zavolá destructor a nechá objekt ve stavu "Zombie". Momentální implementace:
procedure TObject.DisposeOf;
type
TDestructorProc = procedure (Instance: Pointer; OuterMost: ShortInt);
begin
{$IFDEF AUTOREFCOUNT}
if Self <> nil then
begin
Self.__ObjAddRef; // Ensure the instance remains alive throughout the disposal process
try
if __SetDisposed(Self) then
begin
_BeforeDestruction(Self, 1);
TDestructorProc(PPointer(PByte(PPointer(Self)^) + vmtDestroy)^)(Self, 0);
end;
finally
Self.__ObjRelease; // This will deallocate the instance if the above process cleared all other references.
end;
end;
{$ELSE}
Free;
{$ENDIF}
end;
Test zda je objekt disposed je přes property Disposed (viz. změny v TObject). Tedy volání DisposeOf znamená že nás nezajímá zda ještě někdo drží referenci, ale chceme určitě zavolat destruktor, který uvolní např. soubory (paměť se uvolní až to bude možné).
Pole třídy mohu označit jako Weak.
TOwnedCollection = class(TCollection)
private
[Weak] FOwner: TPersistent;
protected
function GetOwner: TPersistent; override;
public
constructor Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
end;
Tento flag znamená, že nezvedá počet referencí a řeší problém kruhových referencí (jinak se to dá řešit i přes DisposeOf).
Detekci kruhových referencí během ladění lze provést za pomoci volání funkce CheckForCycles.
Volatile flag označuje pole, které je modifikováno z jiného vlákna, takže si kompilátor nemůže pro něj dovolit používat optimalizace.
Pokud potřebujete manuálně zvednout nebo snížit počítadlo referencí (jeho stav je přes property RefCount) použijte _ObjAddRef nebo __ObjRelease.
Zda někdy bude ARC i pro Windows je ve hvězdách.