Peering

In the Objective-C runtime, both classes and objects are referenced by memory pointers. In the .NET runtime, pointers are rarely (let's say never) manipulated directly. To make the bridge as transparent as possible, the Monobjc bridge provides a peering system that allows bi-directionnal reference between Objective-C and .NET objects. Every Objective-C object that is manipulated in the .NET runtime is associated to a managed wrapper that holds its pointer in an IntPtr field.

This wrapping allows simple manipulation and method calls to existing Objective-C objects. Note that:

  • Every managed wrapper holds a pointer to a corresponding Objective-C object. When a managed wrapper is created, a native Objective-C object is created and wrapped.
  • An Objective-C object is wrapped in a managed wrapper only if it is referenced in .NET code. This limits the amount of managed wrappers in .NET runtime.
  • For performance reason, the managed wrappers are cached into the Monobjc bridge. This ensures that a native Objective-C object has one and only one managed wrapper. A managed wrapper is disposed when its underlying native object is deallocated.

Below, an example on how to access to the wrapped native pointer:

Class c = Class.GetClassFromType(typeof(NSArray));
IntPtr clsPtr = c.NativePointer;
NSArray array = NSArray.Array;
IntPtr instancePtr = array.NativePointer;

Exposing .NET Types

For .NET types to be exposed in the Objective-C runtime, the Monobjc bridge creates a native proxy at runtime (thanks to Objective-C runtime API). This proxy will reflect the .NET type (public fields and methods) in the Objective-C runtime, and will contains the needed plumbery to call back from the Objective-C runtime to .NET runtime.

The .NET type exposition allows a seamless integration into the Objective-C runtime as the Monobjc bridge takes care of all the on-the-fly registration and the native-to-managed marshalling. The figure below shows the equivalent Objective-C class generated at runtime from an exposed .NET type:

See Exposing .NET class to know how to expose a .NET type into the Objective-C runtime through the Monobjc bridge.

Lifecycle

To ensure that both the managed and the native sides remains synchronous, the Monobjc bridge uses an interception mecanism to be notified when a native Objective-C object is deallocated. This notification will allow the bridge to remove the corresponding managed wrapper from cache and dispose it. To be notified about dealloaction of native object, the Monobjc bridge inserts at runtime a method interceptor into the deallocation path. The two root classes NSObject and NSProxy are exposed to intercept the "dealloc" message from their native counterparts.

All the dealloc message sent to native objects are forwarded to the Monobjc bridge deallocation function. The function checks whether the instance is managed by the bridge or not. If yes, then the corresponding wrapper is removed from the bridge; if no, nothing is done. In all cases, dealloc message are forwarded to their target. This message interception slows a bit the deallocation process but without noticeable performance hit.

Class Model

The class model used in Monobjc bridge is done to mirror the Objective-C model as the following:

The Id superclass holds the native pointer to the native instance. Both NSObject and NSProxy inherit from Id as they are root classes (from an Objective-C point of view). The Class class is equivalent to the Objective-C Class construct. The Objectice-C protocols are mirrored with .NET interfaces that all inherits from the IManagedWrapper. With this model, the Monobjc bridge addresses in an elegant way the message passing to object instances (instance methods) or to class instances (mostly allocation or meta-class methods).