- 07/12/2017
- 4 minutos para leer
-
-
d
-
d
-
c
-
un
-
El lenguaje Objective-C se basa en los selectores. Un selector es un mensaje que se puede enviar a un objeto o 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 puede invocar directamente un selector utilizando P/Invoke En su lugar, los selectores se envían a una clase o instancia de Objective-C utilizando la funciónobjc_msgSend
.
Para obtener más información sobre los mensajes en Objective-C, echa un vistazo a la guía Trabajar con objetos de Apple.
Ejemplo
Supongamos que desea invocar el selectorsizeWithFont: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 retorno es
CGSize
para la API unificada. - El parámetro
font
es un UIFont (y un tipo (indirectamente) derivado de NSObject, y se asigna al Sistema.IntPtr. - El parámetro
width
, aCGFloat
, se asigna anfloat
. - El parámetro
lineBreakMode
, aUILineBreakMode
, ya se ha enlazado en Xamarin.iOS como enumeraciónUILineBreakMode
.
Juntando todo, la declaración objc_msgSend
debe coincidir:
CGSize objc_msgSend( IntPtr target, IntPtr selector, IntPtr font, nfloat width, UILineBreakMode mode);
Declararlo de la siguiente manera:
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 anterior SizeF
utilizada antes de cambiar a las API unificadas), 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 de menos de 8 bits de tamaño, declare la función 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 );
Invocar un selector
Invocar un selector tiene tres pasos:
- Consigue el selector objetivo.
- Obtenga el nombre del selector.
- Llame a
objc_msgSend
con los argumentos apropiados.
Destinos selectores
Un destino selector es una instancia de objeto o una clase Objective-C. Si el objetivo es una instancia y proviene de un Xamarin enlazado.Tipo iOS, utilice la propiedad ObjCRuntime.INativeObject.Handle
.
Si el destino es una clase, use ObjCRuntime.Class
para obtener una referencia a la instancia de clase, luego use la propiedad Class.Handle
.
Los nombres de selectores
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 se pueden omitir.
Una vez que tenga un nombre de selector, puede crear una instancia ObjCRuntime.Selector
para él.
Llamar a objc_msgSend
objc_msgSend
envía un mensaje (selector) a un objeto. Esta familia de funciones toma al menos dos argumentos requeridos: el destino del selector (un identificador de instancia o clase), el selector en sí y cualquier argumento requerido para el selector. Los argumentos de instancia y selector deben serSystem.IntPtr
, y todos los argumentos restantes deben coincidir con el tipo que espera el selector, por ejemplo, un nint
para un int
, o unSystem.IntPtr
para todos los tipos derivados de NSObject
. Utilice la propiedadNSObject.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 devuelvan una estructura. En ARM, esto incluye todos los tipos de retorno que no son una enumeración ni ninguno de los tipos integrados en 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 selectores que devuelvan un valor de coma flotante solo en x86. Esta función no necesita ser utilizada en ARM; en su lugar, useobjc_msgSend
. - La función mainobjc_msgsend se utiliza para todos los demás selectores.
Una vez que haya decidido a qué función objc_msgSend
necesita llamar(el simulador y el dispositivo pueden requerir un método diferente), puede usar un método normal para declarar la función para una invocación posterior.
En ObjCRuntime.Messaging
se puede encontrar un conjunto de declaraciones prefabricadasobjc_msgSend
.
Diferentes invocaciones en simulador y dispositivo
Como se describió anteriormente, Objective-C tiene tres tipos de métodos objc_msgSend
: uno para invocaciones regulares, uno para invocaciones que devuelven valores de puntos flotantes (solo x86) y uno para invocaciones que devuelven valores de estructura. Este último incluye el sufijo _stret
enObjCRuntime.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 el primer parámetro como un 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 sus 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);}
Usando el método objc_msgSend_stret
Al compilar para ARM, useobjc_msgSend_stret
para cualquier tipo de valor que no sea una enumeración o cualquiera de los tipos base para una enumeración (int
, byte
, short
, long
, double
, float
).
Al compilar para x86, useobjc_msgSend_stret
para cualquier tipo de valor que no sea una enumeración o cualquiera de los tipos base para una enumeración (int
, byte
, short
, long
, double
, float
)y cuyo tamaño nativo es mayor de 8 bytes.
Crear sus propias firmas
El siguiente gist se puede usar para crear sus propias firmas, si es necesario.