Objective-C selettori in Xamarin.iOS

La Objective-C lingua si basa sui selettori. Un selettore è un messaggio che può essere inviato a un oggetto o a una classe. Xamarin.iOS esegue il mapping dei selettori di istanza ai metodi di istanza e ai selettori di classe ai metodi statici.

A differenza delle normali funzioni C (e come le funzioni membro C++), non è possibile richiamare direttamente un selettore usando P/Invoke , i selettori vengono inviati a una classe o a un'istanza Objective-C usando objc_msgSend Funzione.

Per altre informazioni sui messaggi in Objective-C, vedere la guida Sull'uso degli oggetti di Apple.

Esempio

Si supponga di voler richiamare il sizeWithFont:forWidth:lineBreakMode: selettore in NSString. La dichiarazione (dalla documentazione di Apple) è:

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

Questa API presenta le caratteristiche seguenti:

  • Il tipo restituito è CGSize per l'API unificata.
  • Il font parametro è un oggetto UIFont (e un tipo (indirettamente) derivato da NSObject ed è mappato a System.IntPtr.
  • Il width parametro , un CGFloatoggetto , viene mappato a nfloat.
  • Il lineBreakMode parametro , un UILineBreakMode, è già stato associato in Xamarin.iOS come UILineBreakMode Enumerazione.

Mettendo tutto insieme, la objc_msgSend dichiarazione deve corrispondere:

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

Dichiararlo come segue:

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

Per chiamare questo metodo, usare codice come il seguente:

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 il valore restituito fosse una struttura di dimensioni inferiori a 8 byte (come quella precedente SizeF usata prima di passare alle API unificate), il codice precedente sarebbe stato eseguito nel simulatore, ma si è verificato un arresto anomalo del dispositivo. Per chiamare un selettore che restituisce un valore inferiore a 8 bit di dimensioni, dichiarare la objc_msgSend_stret funzione:

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

Per chiamare questo metodo, usare codice come il seguente:

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

Chiamata di un selettore

La chiamata di un selettore prevede tre passaggi:

  1. Ottenere la destinazione del selettore.
  2. Ottenere il nome del selettore.
  3. Chiamare objc_msgSend con gli argomenti appropriati.

Destinazioni del selettore

Una destinazione del selettore è un'istanza dell'oggetto o una Objective-C classe. Se la destinazione è un'istanza e proviene da un tipo Xamarin.iOS associato, usare la ObjCRuntime.INativeObject.Handle proprietà .

Se la destinazione è una classe, usare ObjCRuntime.Class per ottenere un riferimento all'istanza della classe , quindi usare la Class.Handle proprietà .

Nomi del selettore

I nomi dei selettori sono elencati nella documentazione di Apple. Ad esempio, NSString include sizeWithFont: e sizeWithFont:forWidth:lineBreakMode: selettori. I due punti incorporati e finali fanno parte del nome del selettore e non possono essere omessi.

Dopo aver ottenuto un nome del selettore, è possibile crearne un'istanza ObjCRuntime.Selector .

Chiamata di objc_msgSend

objc_msgSend invia un messaggio (selettore) a un oggetto . Questa famiglia di funzioni accetta almeno due argomenti obbligatori: la destinazione del selettore (un'istanza o un handle di classe), il selettore stesso e tutti gli argomenti necessari per il selettore. Gli argomenti dell'istanza e del selettore devono essere System.IntPtre tutti gli argomenti rimanenti devono corrispondere al tipo previsto dal selettore, ad esempio per nint un intoggetto o per System.IntPtr tutti i NSObjecttipi derivati da . Usare il NSObject.Handle per ottenere un oggetto per un'istanza IntPtrObjective-C del tipo.

È presente più di una objc_msgSend funzione:

  • Usare objc_msgSend_stret per i selettori che restituiscono uno struct. In ARM sono inclusi tutti i tipi restituiti che non sono un'enumerazione o uno qualsiasi dei tipi predefiniti C (char, short, longint, float, , ). double In x86 (simulatore), questo metodo deve essere usato per tutte le strutture di dimensioni superiori a 8 byte (CGSize è di 8 byte e non viene usato objc_msgSend_stret nel simulatore).
  • Usare objc_msgSend_fpret per i selettori che restituiscono un valore a virgola mobile solo su x86. Questa funzione non deve essere usata in ARM; Usare invece objc_msgSend.
  • La funzione objc_msgSend principale viene usata per tutti gli altri selettori.

Dopo aver deciso quali objc_msgSend funzioni è necessario chiamare (simulatore e dispositivo possono richiedere un metodo diverso), è possibile usare un metodo normale [DllImport] per dichiarare la funzione per una chiamata successiva.

Un set di dichiarazioni predefinite objc_msgSend è disponibile in ObjCRuntime.Messaging.

Chiamate diverse nel simulatore e nel dispositivo

Come descritto in precedenza, Objective-C include tre tipi di objc_msgSend metodi: uno per le chiamate regolari, uno per le chiamate che restituiscono valori a virgola mobile (solo x86) e uno per le chiamate che restituiscono valori di struct. Quest'ultimo include il suffisso _stret in ObjCRuntime.Messaging.

Se si richiama un metodo che restituisce determinati struct (regole descritte di seguito), è necessario richiamare il metodo con il valore restituito come primo parametro come out valore:

// 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 regola per quando usare il _stret_ metodo è diversa da x86 e ARM. Se si desidera che le associazioni funzionino sia sul simulatore che sul dispositivo, aggiungere codice come il seguente:

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

Utilizzo del metodo objc_msgSend_stret

Durante la compilazione per ARM, usare objc_msgSend_stretper qualsiasi tipo di valore che non è un'enumerazione o uno qualsiasi dei tipi di base per un'enumerazione (int, byte, shortlong, double). float

Quando si compila per x86, usare objc_msgSend_stretper qualsiasi tipo valore che non è un'enumerazione o uno dei tipi di base per un'enumerazione (int, byte, shortlong, doublefloat) e la cui dimensione nativa è maggiore di 8 byte.

Creazione di firme personalizzate

Il gist seguente può essere usato per creare firme personalizzate, se necessario.