Cross Platform
Android
iOS
Mac
Test Cloud

Objective-C Selectors

The Objective-C language is based upon selectors. A selector is a message that can be sent to an object or a class. Xamarin.iOS maps instance selectors to instance methods, and class selectors to static methods.

Unlike normal C functions (and like C++ member functions), you cannot directly invoke a selector using P/Invoke. (Aside: in theory you could use P/Invoke for non-virtual C++ member functions, but you'd need to worry about per-compiler name mangling, which is a world of pain better ignored.) Instead, selectors are sent to an Objective-C class or instance using the objc_msgSend function.

You may find this helpful guide on Objective-C messaging useful.

Example

Suppose you want to invoke the -[NSString sizeWithFont:forWidth:lineBreakMode:] selector. The declaration (from Apple's documentation) is:

- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode

Put it all together, and we want an objc_msgSend declaration that matches:

SizeF objc_msgSend(IntPtr target, IntPtr selector,
    IntPtr font, float width, UILineBreakMode mode);

Checking the MonoTouch.ObjCRuntime.Messaging members, we don't see a match for this prototype. Consequently, we will need to declare it ourself:

[DllImport (MonoTouch.Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend")]
static extern SizeF cgsize_objc_msgSend_IntPtr_float_int (
    IntPtr target, IntPtr selector,
    IntPtr font,
    float width,
    UILineBreakMode mode);

Once declared, we can invoke it once we have the appropriate parameters:

NSString      target = ...
Selector    selector = new Selector ("sizeWithFont:forWidth:lineBreakMode:");
UIFont          font = ...
float          width = ...
UILineBreakMode mode = ...

SizeF size = cgsize_objc_msgSend_IntPtr_float_int(
    target.Handle, selector.Handle,
    font == null ? IntPtr.Zero : font.Handle,
    width,
    mode);

Unfortunately, while this works under the simulator it will crash on the device (because SizeF is a structure that's <= 8 bytes in size; see The Ugly for details). Consequently we also need to declare the objc_msgSend_stret() function:

[DllImport (MonoTouch.Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend_stret")]
static extern void cgsize_objc_msgSend_stret_IntPtr_float_int (
    out SizeF retval, 
    IntPtr target, IntPtr selector,
    IntPtr font,
    float width,
    UILineBreakMode mode);

Invocation now becomes:

NSString      target = ...
Selector    selector = new Selector ("sizeWithFont:forWidth:lineBreakMode:");
UIFont          font = ...
float          width = ...
UILineBreakMode mode = ...

SizeF size;

if (Runtime.Arch == Arch.SIMULATOR)
    size = cgsize_objc_msgSend_IntPtr_float_int(
        target.Handle, selector.Handle,
        font == null ? IntPtr.Zero : font.Handle,
        width,
        mode);
else
    cgsize_objc_msgSend_stret_IntPtr_float_int(
        out size,
        target.Handle, selector.Handle,
        font == null ? IntPtr.Zero: font.Handle,
        width,
        mode);

Invoking a Selector

Invoking a selector has three steps:

  1. Get the selector target.
  2. Get the selector name.
  3. Call objc_msgSend() with the appropriate arguments.

Selector Targets

A selector target is either an object instance or an Objective-C class. If the target is an instance and came from a bound Xamarin.iOS type, use the MonoTouch.ObjCRuntime.INativeObject.Handle property.

If the target is a class, use MonoTouch.ObjCRuntime.Class to get a reference to the class instance, then use the Class.Handle property.

Selector Names

Selector names are listed within Apple's documentation. For example, the UIKit NSString extension methods include sizeWithFont: and sizeWithFont:forWidth:lineBreakMode:. The embedded and trailing colons are important, and are part of the selector name.

Once you have a selector name, you can create a MonoTouch.ObjCRuntime.Selector instance for it.

Calling objc_msgSend()

objc_msgSend() is used to send a message (selector) to an object. This family of functions takes at least two required arguments: the selector target (an instance or class handle), the selector itself, and then any arguments required for the particular selector. The instance and selector arguments must be System.IntPtr, and all remaining arguments must match the type the selector expects, e.g. an int for an int, or a System.IntPtr for all NSObject-derived types. Use the NSObject.Handle property to obtain an IntPtr for an Objective-C type instance.

Unfortunately, there is more than one objc_msgSend() function.

Use objc_msgSend_stret() for selectors which return a structure. To keep things "interesting", on ARM this includes all return types that are not an enumeration or any of the C builtin types (char, short, int, long, float, double). On x86 (the Simulator), this needs to be used for all structures larger than 8 bytes in size. (CGSize -- used in the example above -- is 8 bytes exactly, and thus doesn't use objc_msgSend_stret() in the simulator.)

Use objc_msgSend_fpret() for selectors which return a floating point value on x86 only. This function does not need to be used on ARM; instead, use objc_msgSend().

The main objc_msgSend() function is used for all other selectors.

Once you've decided which objc_msgSend() function(s) you need to call (and it may be more than one, e.g. for simulator and device), you can use a normal [DllImport] method to declare the function for later invocation.

A set of pre-made objc_msgSend() declarations can be found in MonoTouch.ObjCRuntime.Messaging.

The Ugly

Objective-C has three kinds of objc_msgSend methods: one for regular invocations, one for invocations that return floating point values (x86 only), and one for invocations that return struct values. The latter includes the suffix _stret in MonoTouch.ObjCRuntime.Messaging.

If you are invoking a method that will return certain structures (rules are described below), you must invoke the method with the return value as the first parameter as an out value, like this:

// The following returns a PointF structure:
PointF ret;
Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, this.Handle, selConvertPointFromWindow.Handle, point, window.Handle);

Things get ugly here and because the rule for when you must use _stret is different on X86 and ARM, if you want your bindings to work on both the simulator and the device, you need to add code that looks like this:

if (Runtime.Arch == Arch.DEVICE){
    PointF ret;

    Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, myHandle, selector.Handle);

    return ret;
} else
    return Messaging.PointF_objc_msgSend_PointF_IntPtr (myHandle, selector.Handle);

Using the objc_msgSend_stret method

The rule for when to use the objc_msgSend_stret are like this for ARM:

  • Any value type that is not an enumeration or any of the base types for an enumeration (int, byte, short, long, double, float).

The rule for when to use the objc_msgSend_stret are like this for X86:

  • Any value type that is not an enumeration, or any of the base types for an enumeration (int, byte, short, long, double, float) and whose native size is larger than 8 bytes.