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ů.
rozšíření TObject
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;
Dispose
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é).
Weak a Volatile
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.
Počítání referencí manuálně
Pokud potřebujete manuálně zvednout nebo snížit počítadlo referencí (jeho stav je přes property RefCount) použijte _ObjAddRef nebo __ObjRelease.
Závěr
Zda někdy bude ARC i pro Windows je ve hvězdách.