Life-Time Strategies
Life-Time Strategies are a way to control how the life-time of objects is managed, i.e. to determine how an object gets generated and released.
By default, each Island sub-platform and/or Object Model defines a default life-time strategy to be used, and it is very rare that developers would everneed to override that in normal application code.
To explicitly specify a life-time strategy for an individual field or variable, the Oxygene lifetimestrategy
keyword can be used as a type modifier, similar to how Storage Modifiers are used in the Cocoa platform. The keyword takes the name of a life-time strategy as parameter in parentheses.
var x: lifetimestrategy(Manual) String;
Since this is longwinded to type, and only available in the Oxygene language, Island RTL provides a generic alias for each life-time strategy, that can be used to wrap an object, instead:
var x: Manual<String> := ...;
Manual<String> x = ...;
let x: Manual<String> = ...
Manual<String> x = ...;
Dim x As Manual(Of String) = ...
For the example above, the Manual<T>
alias is defined in Island RTL like this:
type
Manual<T> = public lifetimestrategy(Manual) T;
Available Life-Time Strategies
Island RTL predefines these strategies:
Manual
: When used, allocates the object on the heap without any life-time management. Objects need to be explicitly freed by callingManual.FreeObject()
, like they would be in more classic development environments such as C or Delphi.RC
: Uses reference counting for the object and frees it automatically when the last reference to the object goes tonil
/null
.BoehmGC
: Uses the Boehm Garbage Collector to allocate and cleanup the objects. This is the default for native Island objects, on all platforms other than WebAssembly.ForeignBoehmGC
: Same as BoehmGC but used when a Garbage-Collected reference is stored in a non-garbage-collected object, like for example a Cocoa class. This ensures it's properly taken in account in both GC and the Referende Counting life-cycle of the containing object.SimpleGC
: A simple single-threaded garbage collector implementation for WebAssembly, used as the default on that sub-platform.ObjcStrong
andObjcWeak
: Automatically used for the Cocoa class model when using strong and weak references in cocoa using the respective Storage ModifiersCOM
: Used for COM Interface support, to manage life-time via calls to AddRef/Release calls to theIUnknown
interface.
Lifetime strategies are not compatible with each other without an explicit conversion operator defined on the strategies themselves. Right now, only the ObjcStrong
/ObjcWeak
strategies support this.
Aspects
Three special Aspects are provided to work with life-time strategies:
DefaultObjectLifetimeStrategy
: defines the default lifetime strategy for a project. Only 1 of these can be applied, and the Island RTL binary does ths for you.GCSkipIfOnStack
: skip this lifetime strategy's management if the target can be proven to remain on the stack. Useful for scanning garbage collectors.LifetimeStrategyOverride
: Globally override the lifetime of a pointer type to use another strategy. This can be used, for example, to turn an opaque Win32 pointer type such asHString
into a native island type managed by the specified lifetime strategy.
Implementing Life-Time Strategies
Generally, developers will not have to implement a strategy themselves, but Elements does provide the provision to do so, if needed.
Life-Time Strategies are Records/Structs that implement the ILifetimeStrategy<T>
and a Finalizer and Constructors, if needed. They should have a single field of type IntPtr
, that can hold the pointer for the maintained object. The compiler will do the work of casting this to the real type.
For example, the RC
Reference-Counting Life-Time Strategy is defined like such:
type
RC<T> = public lifetimestrategy (RC) T; // Alias
RC = public record(ILifetimeStrategy<RC>)
private
fValue: IntPtr;
public
class method &New(aTTY: ^Void; aSize: IntPtr): ^Void;
begin
result := ^Void(malloc(aSize + sizeOf(^Void)));
^UIntPtr(result)^ := 1;
result := result + sizeOf(^Void);
^^Void(result)^ := aTTY;
memset(^Byte(result) + sizeOf(^Void), 0, aSize - sizeOf(^Void));
end;
class method &Copy(var aDest, aSource: RC);
begin
var lSrc := aSource.fValue;
if lSrc = 0 then exit;
InternalCalls.Increment(var ^IntPtr(lSrc)[-1]);
aDest.fValue := lSrc;
end;
constructor &Copy(var aSource: RC);
begin
var lSrc := aSource.fValue;
if lSrc = 0 then exit;
InternalCalls.Increment(var ^IntPtr(lSrc)[-1]);
fValue := lSrc;
end;
class operator Assign(var aDest: RC; var aSource: RC);
begin
Assign(var aDest, var aSource);
end;
class method Assign(var aDest, aSource: RC);
begin
if (@aDest) = (@aSource) then exit;
var lInst := aSource.fValue;
var lOld := InternalCalls.Exchange(var aDest.fValue, lInst);
if lOld = lInst then exit;
if lInst <> 0 then begin
InternalCalls.Increment(var ^IntPtr(lInst)[-1]);
end;
if lOld <> 0 then begin
if InternalCalls.Decrement(var ^IntPtr(lOld)[-1]) = 0 then
FreeObject(lOld);
end;
end;
finalizer;
begin
var lValue := InternalCalls.Exchange(var fValue, 0);
if lValue = 0 then exit;
var p := InternalCalls.Decrement(var ^IntPtr(lValue)[-1]);
if p = 0 then
FreeObject(p);
end;
class method Init(var Dest: RC); empty;
class method Release(var Dest: RC);
begin
var lValue := InternalCalls.Exchange(var Dest.fValue, 0);
if lValue = 0 then exit;
var p := InternalCalls.Decrement(var ^IntPtr(lValue)[-1]);
if p = 0 then
FreeObject(p);
end;
class method FreeObject(aObj: IntPtr); private;
begin
if aObj = 0 then exit;
try {$HIDE W58}
InternalCalls.Cast<Object>(^Void(aObj)).Finalize;
{$SHOW W58}
free(^Void(aObj - sizeOf(IntPtr)));
except
end;
end;
end;
New
gets called when creating a new object instanceInit
gets called when a variable or field is set to the default 'not assigned' value- The
Copy
constructor and method both perform the same, but the constructor is required when usingComGC
directly. This is called when creating a new instance based on another, and it ignores the value in the destination and overwrites it without any action to release it. - The
Assign
operator and method work the same asCopy
, except they releases the old value of the object before assigning. Release
and/or the Finalizer are called when releasing the object.