HSObject - mokus0/objc-ffi GitHub Wiki

The HSObject protocol is an interface through which an objective-C object can expose references to the Haskell garbage collector, allowing the object itself to be collected by the garbage collector if its only retainers are Haskell closures. The central mechanism for this is constructing and managing a Haskell closure which reifies the object's references (and serves as the Haskell-side representation of the object as well). This closure is called an "HSO", short for "Haskell Side Object".

Managing an HSO is quite delicate, so there is a standard system for implementing all of the HSObject protocol - when defining a class, call registerHSObjectClass rather than objc_registerClassPair, passing it an initializer for the haskell-side reference data.

Objective-C client code need not be aware of the HSObject system at all, but if you wish to enable HSObjects to be garbage collected it is recommended to use importObject to wrap all objective-C objects in HSOs. This has very little overhead for normal (non-HSObject) objects and takes care of all retaining and releasing of objects, normal or otherwise, so it is recommended to use this system even if you don't need HSObject support.

the gory details

Each class in an object's ancestry is able to allocate a single Haskell closure for each instance, considered to represent its "instance data", which are fully garbage-collectable Haskell values - in particular, they can safely reference the object's HSO itself. The HSObject system is responsible for implementing this feature, with the following design goals in mind:

  • Haskell-side instance data must be unique
  • Haskell-side instance data must be initialized exactly once and held for the lifetime of the instance
  • References from haskell-side instance data should be traversable by Haskell's garbage collector, so that if the only reference to an object is (possibly indirectly) from its own instance data it will be collected as expected.

As a consequence of these goals,

  • An object must not (strongly) reference its haskell-side instance data while a haskell-side reference to it exists. That would defeat garbage collection in the event that the instance data indirectly refers to the haskell-side reference.
  • An object must (strongly) reference its haskell-side instance data while a haskell-side reference to it does not exist. Otherwise there is nothing to keep that haskell-side data alive.

Current implementation

The current implementation centers around a type, HSO, which contains a ForeignPtr to the object and a list of all haskell-side instance data. This is a single closure that references all of the object's Haskell-side instance data and is itself referenced as a handle to the object, so it is an accurate representation of the object's reference graph for purposes of garbage collection.

Uniqueness of the instance data is achieved by the stronger fact of uniqueness of the HSOs - through a fairly delicate dance, each object keeps a reference to its own HSO. The delicacy comes from the need to dynamically switch the reference from a "weak" reference to a "strong" one depending on whether there the object has any non-Haskell retainers.

When __hsGetSelf: is called, the object initializes its HSO if necessary and returns it, and indicates via the Bool * out-parameter whether the returned HSO is newly-created. Initialization is done by a call to __hsInit. __hsInit implementations should chain to superclass implementations, if any exist.

The retain method of a Haskell-implemented object checks to see if the HSO has been created. If so, it will create a stable ptr to it and stash that stable ptr in a private instance variable, effectively retaining itself. The release method, after invoking [super release], will check to see whether the retain count has been reduced to one. If it has, then it frees that stable pointer (if there is one set), because it knows that the reference from Haskell is the only remaining reference to itself. It then becomes possible for the garbage collector to free the HSO. Eventually either the object will be retained again by some other objective-C code or the HSO will be freed by the Haskell garbage collector. When the GC decides to collect the HSO the weak reference in the object map will be finalized. That finalizer then sends the object its final release message and removes the now-dead weak reference from the object map.

For still more gory details, see HSObject.m where the HSObject protocol is defined or HSObject.hs where registerHSObjectClass is implemented.

Illustrations

For sake of illustration, consider four classes creatively named A, B, C and D:

class A         (implemented in ObjC)
class B : A     (implemented in Haskell)
class C : B     (implemented in ObjC)
class D : C     (implemented in Haskell)

If an instance of a plain Objective C class such as A is referenced in Haskell code, the references created look like this:

hso-objc.png

For instances of a Haskell-implemented class such as D, the reference graph is a bit more complex:

hso-hs.png

Changes under consideration

It may be worth having ivars in the B and D classes that weakly reference their corresponding haskell-side instance data. It would make the objects slightly larger but would allow quicker retrieval of the Haskell-side data, especially for deeply-inherited classes. Note that these references would not be enough to allow the weak reference to the ID to be dropped though, unless you also sacrifice uniqueness of the ID (so that the object could create a new one and make a StablePtr to that one when it gets retained), which would in turn entail tracking how many of the object's retainers are Haskell references and changing the condition for existence of __retainedHaskellPart__ to something like hsRetainCount != retainCount.

It also may be worth adding a self parameter to the instance data initializer passed to registerHSObjectClass.