Objective-C seletores no Xamarin.iOS
O Objective-C idioma é baseado em seletores. Um seletor é uma mensagem que pode ser enviada para um objeto ou uma classe. O Xamarin.iOS mapeia seletores de instância para métodos de instância e seletores de classe para métodos estáticos.
Ao contrário das funções C normais (e como as funções membro C++), você não pode invocar diretamente um seletor usando P/Invoke . Em vez disso, os seletores são enviados para uma Objective-C classe ou instância usando oobjc_msgSend
Função.
Para obter mais informações sobre mensagens no Objective-C, dê uma olhada no guia Trabalhando com Objetos da Apple.
Exemplo
Suponha que você queira invocar osizeWithFont:forWidth:lineBreakMode:
seletor em NSString
.
A declaração (da documentação da Apple) é:
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
Essa API tem as seguintes características:
- O tipo de retorno é
CGSize
para a API Unificada. - O
font
parâmetro é um UIFont (e um tipo (indiretamente) derivado de NSObject e é mapeado para System.IntPtr. - O
width
parâmetro , umCGFloat
, é mapeado paranfloat
. - O
lineBreakMode
parâmetro , umUILineBreakMode
, já foi associado no Xamarin.iOS como oUILineBreakMode
Enumeração.
Juntando tudo isso, a objc_msgSend
declaração deve corresponder:
CGSize objc_msgSend(
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
Declare-o da seguinte maneira:
[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 chamar esse método, use código como o seguinte:
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
);
Se o valor retornado fosse uma estrutura com menos de 8 bytes de tamanho (como o mais antigo SizeF
usado antes de alternar para as APIs Unificadas), o código acima teria sido executado no simulador, mas falhou no dispositivo. Para chamar um seletor que retorna um valor menor que 8 bits de tamanho, declare a objc_msgSend_stret
função :
[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 chamar esse método, use código como o seguinte:
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
);
Invocando um seletor
Invocar um seletor tem três etapas:
- Obtenha o destino do seletor.
- Obtenha o nome do seletor.
- Chame
objc_msgSend
com os argumentos apropriados.
Destinos do seletor
Um destino de seletor é uma instância de objeto ou uma Objective-C classe . Se o destino for uma instância e for proveniente de um tipo Xamarin.iOS associado, use a ObjCRuntime.INativeObject.Handle
propriedade .
Se o destino for uma classe, use ObjCRuntime.Class
para obter uma referência à instância de classe e use a Class.Handle
propriedade .
Nomes de seletor
Os nomes dos seletores estão listados na documentação da Apple. Por exemplo, NSString
inclui sizeWithFont:
seletores e sizeWithFont:forWidth:lineBreakMode:
. Os dois-pontos inseridos e à direita fazem parte do nome do seletor e não podem ser omitidos.
Depois de ter um nome de seletor, você pode criar uma ObjCRuntime.Selector
instância para ele.
Chamando objc_msgSend
objc_msgSend
envia uma mensagem (seletor) para um objeto . Essa família de funções usa pelo menos dois argumentos necessários: o destino do seletor (uma instância ou um identificador de classe), o seletor em si e todos os argumentos necessários para o seletor. Os argumentos de instância e seletor devem ser System.IntPtr
e todos os argumentos restantes devem corresponder ao tipo esperado pelo seletor, por exemplo, um nint
para um int
ou um System.IntPtr
para todos os NSObject
tipos derivados de . Use oNSObject.Handle
para obter um IntPtr
para uma Objective-C instância de tipo.
Há mais de uma objc_msgSend
função:
- Use
objc_msgSend_stret
para seletores que retornam um struct. No ARM, isso inclui todos os tipos de retorno que não são uma enumeração ou qualquer um dos tipos internos C (char
,short
,int
,long
, ,double
float
). Em x86 (o simulador), esse método precisa ser usado para todas as estruturas com mais de 8 bytes de tamanho (CGSize
tem 8 bytes e não usaobjc_msgSend_stret
no simulador). - Use
objc_msgSend_fpret
para seletores que retornam um valor de ponto flutuante somente em x86. Essa função não precisa ser usada no ARM; Em vez disso, useobjc_msgSend
. - A função main objc_msgSend é usada para todos os outros seletores.
Depois de decidir quais objc_msgSend
funções você precisa chamar (simulador e dispositivo podem exigir um método diferente), você pode usar um método normal [DllImport]
para declarar a função para invocação posterior.
Um conjunto de declarações pré-feitas objc_msgSend
pode ser encontrado em ObjCRuntime.Messaging
.
Invocações diferentes no simulador e no dispositivo
Conforme descrito acima, Objective-C tem três tipos de objc_msgSend
métodos: um para invocações regulares, um para invocações que retornam valores de ponto flutuante (somente x86) e outro para invocações que retornam valores de struct. Este último inclui o sufixo _stret
em ObjCRuntime.Messaging
.
Se você estiver invocando um método que retornará determinados structs (regras descritas abaixo), deverá invocar o método com o valor retornado como o primeiro parâmetro como um 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);
A regra para quando usar o _stret_
método difere em x86 e ARM.
Se você quiser que suas associações funcionem no simulador e no dispositivo, adicione um código como o seguinte:
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 o método objc_msgSend_stret
Ao compilar para ARM, use oobjc_msgSend_stret
para qualquer tipo de valor que não seja uma enumeração ou qualquer um dos tipos base para uma enumeração (int
, byte
, short
, long
, double
, float
).
Ao compilar para x86, useobjc_msgSend_stret
para qualquer tipo de valor que não seja uma enumeração ou qualquer um dos tipos base para uma enumeração (int
, byte
, short
, long
, double
, float
) e cujo tamanho nativo é maior que 8 bytes.
Criando suas próprias assinaturas
O gist a seguir pode ser usado para criar suas próprias assinaturas, se necessário.