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 , um CGFloat, é mapeado para nfloat.
  • O lineBreakMode parâmetro , um UILineBreakMode, 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:

  1. Obtenha o destino do seletor.
  2. Obtenha o nome do seletor.
  3. 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.IntPtre todos os argumentos restantes devem corresponder ao tipo esperado pelo seletor, por exemplo, um nint para um intou um System.IntPtr para todos os NSObjecttipos 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, , doublefloat). 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 usa objc_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, use objc_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.