Objective-C selectores en Xamarin.iOS

El Objective-C idioma se basa en los Objective-C. Un selector es un mensaje que se puede enviar a un objeto o a una clase. Xamarin.iOS asigna selectores de instancia a métodos de instancia y selectores de clases a métodos estáticos.

A diferencia de las funciones normales de C (y como las funciones miembro de C++), no se puede invocar directamente un selector mediante P/Invoke En su lugar, los selectores se envían a una clase o instancia mediante elobjc_msgSend Función.

Para obtener más información sobre los mensajes en , consulte la guía Objective-C Trabajar con objetos Objective-C Apple.

Ejemplo

Supongamos que desea invocar elsizeWithFont:forWidth:lineBreakMode: selector en NSString . La declaración (de la documentación de Apple) es:

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

Esta API tiene las siguientes características:

  • El tipo de valor devuelto CGSize es para el Unified API.
  • El font parámetro es UN font (y un tipo (indirectamente) derivado de NSObjecty se asigna a System.IntPtr.
  • El width parámetro , un , se asigna a CGFloatnfloat .
  • El lineBreakMode parámetro , un , ya se ha enlazado en UILineBreakMode Xamarin.iOS comoUILineBreakMode Enumeración.

Si se reúne todo, la objc_msgSend declaración debe coincidir con:

CGSize objc_msgSend(
    IntPtr target,
    IntPtr selector,
    IntPtr font,
    nfloat width,
    UILineBreakMode mode
);

Declaro esto de la siguiente manera:

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

Para llamar a este método, use código como el siguiente:

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

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

Si el valor devuelto fuera una estructura de menos de 8 bytes de tamaño (como la anterior usada antes de cambiar a las API unificadas), el código anterior se habría ejecutado en el simulador, pero se bloquearía en el SizeF dispositivo. Para llamar a un selector que devuelve un valor de menos de 8 bits de tamaño, declare la objc_msgSend_stret función :

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

Para llamar a este método, use código como el siguiente:

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

CGSize 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
    );

Invocación de un selector

La invocación de un selector tiene tres pasos:

  1. Obtiene el destino del selector.
  2. Obtenga el nombre del selector.
  3. Llame objc_msgSend a con los argumentos adecuados.

Destinos del selector

Un destino selector es una instancia de objeto o una Objective-C clase. Si el destino es una instancia de y viene de un tipo de Xamarin.iOS enlazado, use la ObjCRuntime.INativeObject.Handle propiedad .

Si el destino es una clase, use ObjCRuntime.Class para obtener una referencia a la instancia de clase y, a continuación, use la Class.Handle propiedad .

Nombres de selector

Los nombres de selector se enumeran en la documentación de Apple. Por ejemplo, NSString incluye sizeWithFont: los sizeWithFont:forWidth:lineBreakMode: selectores y . Los dos puntos incrustados y finales forman parte del nombre del selector y no se pueden omitir.

Una vez que tenga un nombre de selector, puede crear una ObjCRuntime.Selector instancia para él.

Llamada a objc_msgSend

objc_msgSend envía un mensaje (selector) a un objeto . Esta familia de funciones toma al menos dos argumentos necesarios: el destino del selector (un identificador de instancia o clase), el propio selector y los argumentos necesarios para el selector. Los argumentos de instancia y selector deben ser y todos los argumentos restantes deben coincidir con el tipo que espera el selector, por ejemplo, para un o para todos los tipos System.IntPtrnintintSystem.IntPtrNSObject derivados de . Use elNSObject.Handle propiedad para obtener para IntPtr una instancia Objective-C de tipo.

Hay más de una objc_msgSend función:

  • Se objc_msgSend_stret usa para los selectores que devuelven un struct. En ARM, esto incluye todos los tipos devueltos que no son una enumeración ni ninguno de los tipos integrados de C ( char , , , , , shortintlongfloatdouble ). En x86 (el simulador), este método debe usarse para todas las estructuras de más de 8 bytes de tamaño (es 8 bytes y no se usa en CGSizeobjc_msgSend_stret el simulador).
  • Se objc_msgSend_fpret usa para los selectores que devuelven un valor de punto flotante solo en x86. Esta función no necesita usarse en ARM; en su lugar, use objc_msgSend .
  • La función objc_msgSend principal se usa para todos los demás selectores.

Una vez que haya decidido a qué funciones debe llamar (el simulador y el dispositivo pueden requerir un método diferente), puede usar un método normal para declarar la función para su invocación objc_msgSend[DllImport] posterior.

Puede encontrar un conjunto de objc_msgSend declaraciones predefinidas en ObjCRuntime.Messaging .

Diferentes invocaciones en el simulador y el dispositivo

Como se describió anteriormente, tiene tres tipos de métodos: uno para las invocaciones normales, otro para las invocaciones que devuelven valores de punto flotante Objective-C (solo x86) y otro para las invocaciones que devuelven valores objc_msgSend de estructura. El último incluye el sufijo _stret en ObjCRuntime.Messaging .

Si invoca un método que devolverá determinados structs (reglas que se describen a continuación), debe invocar el método con el valor devuelto como primer parámetro como out valor:

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

La regla para cuándo usar el _stret_ método difiere en x86 y ARM. Si desea que los enlaces funcionen tanto en el simulador como en el dispositivo, agregue código como el siguiente:

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);
}

Uso del objc_msgSend_stret método

Al compilar para ARM, use elobjc_msgSend_stret para cualquier tipo de valor que no sea una enumeración ni ninguno de los tipos base de una enumeración ( int , , , , byteshortlongdoublefloat ).

Al compilar para x86, useobjc_msgSend_stret para cualquier tipo de valor que no sea una enumeración ni ninguno de los tipos base de una enumeración ( , , , , ) y cuyo tamaño nativo sea superior a intbyteshortlongdoublefloat 8 bytes.

Creación de sus propias firmas

El siguiente gist se puede usar para crear sus propias firmas, si es necesario.