Selectores Objective-C en Xamarin.iOS
El lenguaje Objective-C se basa en selectores. Un selector es un mensaje que se puede enviar a un objeto o una clase. Xamarin.iOS asigna selectores de instancias 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 Objective-C mediante la función objc_msgSend
.
Para obtener más información sobre los mensajes de Objective-C, eche un vistazo a la guía Trabajo con objetos de Apple.
Ejemplo
Supongamos que desea invocar el selector sizeWithFont:forWidth:lineBreakMode:
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 es
CGSize
para Unified API. - El parámetro
font
es un UIFont y un tipo (indirectamente) derivado de NSObjecty se asigna a System.IntPtr. - El parámetro
width
, unCGFloat
, se asigna anfloat
. - El parámetro
lineBreakMode
, unUILineBreakMode
, ya se ha enlazado en Xamarin.iOS como la enumeraciónUILineBreakMode
.
Juntándolo todo, la declaración objc_msgSend
debería coincidir:
CGSize objc_msgSend(
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
Declárelo 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 hubiera sido una estructura de menos de 8 bytes de tamaño (como la antigua SizeF
utilizada antes de cambiar a Unified API), el código anterior se habría ejecutado en el simulador pero se habría bloqueado en el dispositivo. Para llamar a un selector que devuelve un valor inferior a 8 bits de tamaño, declare la función objc_msgSend_stret
:
[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:
- Obtener el destino del selector.
- Obtener el nombre del selector.
- Llamar a
objc_msgSend
con los argumentos adecuados.
Destinos del selector
Un destino de selector es una instancia de objeto o una clase Objective-C. Si el destino es una instancia y procede de un tipo de Xamarin.iOS enlazado, utilice la propiedad ObjCRuntime.INativeObject.Handle
.
Si el destino es una clase, use ObjCRuntime.Class
para obtener una referencia a la instancia de clase y, a continuación, use la propiedad Class.Handle
.
Nombres de selector
Los nombres de selector se enumeran en la documentación de Apple. Por ejemplo, NSString
incluye selectores sizeWithFont:
y sizeWithFont:forWidth:lineBreakMode:
. Los dos puntos incrustados y finales forman parte del nombre del selector y no pueden omitirse.
Una vez que tenga un nombre de selector, puede crear una instancia ObjCRuntime.Selector
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 System.IntPtr
, y todos los argumentos restantes deben coincidir con el tipo que espera el selector, por ejemplo, un nint
para int
, o un System.IntPtr
para todos los tipos derivados de NSObject
. Utilice el Propiedad NSObject.Handle
para obtener un IntPtr
para una instancia de tipo Objective-C.
Hay más de una función objc_msgSend
:
- Use
objc_msgSend_stret
para selectores que devuelven una estructura. En ARM, esto incluye todos los tipos devueltos que no son una enumeración o ninguno de los tipos integrados de C (char
,short
,int
,long
,float
,double
). En x86 (el simulador), este método debe usarse para todas las estructuras de más de 8 bytes de tamaño (CGSize
es de 8 bytes y no usaobjc_msgSend_stret
en el simulador). - Use
objc_msgSend_fpret
para los selectores que devuelven un valor de coma flotante solo en x86. Esta función no necesita usarse en ARM; en su lugar, useobjc_msgSend
. - La función principal objc_msgSend se usa para todos los demás selectores.
Una vez que haya decidido a qué funciones objc_msgSend
necesita llamar (el simulador y el dispositivo pueden requerir un método diferente), puede usar un método normal [DllImport]
para declarar la función para la invocación posterior.
En ObjCRuntime.Messaging
se puede encontrar un conjunto de declaraciones objc_msgSend
creadas previamente.
Diferentes invocaciones en el simulador y el dispositivo
Como se ha descrito anteriormente, Objective-C tiene tres tipos de métodos objc_msgSend
: uno para las invocaciones normales, una para las invocaciones que devuelven valores de punto flotante (solo x86) y otra para las invocaciones que devuelven valores de estructura. Este último incluye el sufijo _stret
en ObjCRuntime.Messaging
.
Si invoca un método que devolverá ciertas estructuras (reglas descritas a continuación), debe invocar el método con el valor devuelto como primer parámetro como valor out
:
// 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 método _stret_
difiere en x86 y ARM.
Si desea que los enlaces funcionen en el simulador y 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 método objc_msgSend_stret
Al compilar para ARM, use objc_msgSend_stret
para cualquier tipo de valor que no sea una enumeración o ninguno de los tipos base de una enumeración (int
, byte
, short
, long
, double
, float
).
Al compilar para x86, use objc_msgSend_stret
para cualquier tipo de valor que no sea una enumeración o ninguno de los tipos base de una enumeración (int
, byte
, short
, long
, double
, float
) y cuyo tamaño nativo sea mayor que 8 bytes.
Creación de firmas propias
Si es necesario, se puede usar el siguiente gist para crear sus propias firmas.