ARC - Automatic Reference Counting

vložil Radek Červinka 1. června 2013 01:32

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.

Tagy: , ,

Jazyk | Novinky v Delphi

Komentování ukončeno

Naše nabídka

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy).

love Delphi

O Delphi.cz

Delphi je moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX, Linux a na iPhone a Android.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Poslední komentáře

Comment RSS

Dle měsíců