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.ObjcStrongandObjcWeak: 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 theIUnknowninterface.
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 asHStringinto 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;
Newgets called when creating a new object instanceInitgets called when a variable or field is set to the default 'not assigned' value- The
Copyconstructor and method both perform the same, but the constructor is required when usingComGCdirectly. 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
Assignoperator and method work the same asCopy, except they releases the old value of the object before assigning. Releaseand/or the Finalizer are called when releasing the object.