Objective-C Selektoren in Xamarin.iOS

Die Objective-C Sprache basiert auf Selektoren. Ein Selektor ist eine Nachricht, die an ein Objekt oder eine Klasse gesendet werden kann. Xamarin.iOS ordnet instance Selektoren instance Methoden und Klassenselektoren statischen Methoden zu.

Im Gegensatz zu normalen C-Funktionen (und wie C++-Memberfunktionen) können Sie einen Selektor nicht direkt mit P/Invoke aufrufen. Selektoren werden stattdessen mithilfe von an eine Objective-C Klasse oder instance gesendet.objc_msgSend Funktion.

Weitere Informationen zu Nachrichten in finden Sie im Objective-CLeitfaden arbeiten mit Objekten von Apple.

Beispiel

Angenommen, Sie möchten diesizeWithFont:forWidth:lineBreakMode: -Auswahl auf NSString. Die Erklärung (aus der Apple-Dokumentation) lautet:

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

Diese API weist die folgenden Merkmale auf:

  • Der Rückgabetyp ist CGSize für die einheitliche API.
  • Der font Parameter ist ein UIFont (und ein (indirekt) von NSObject abgeleiteter Typ und wird System.IntPtr zugeordnet.
  • Der width -Parameter wird CGFloatzugeordnet nfloat.
  • Der lineBreakMode -Parameter wurde UILineBreakModebereits in Xamarin.iOS als gebunden.UILineBreakMode Enumeration.

Die Deklaration sollte zusammen mit folgenden objc_msgSend Übereinstimmungen übereinstimmen:

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

Deklarieren Sie sie wie folgt:

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

Verwenden Sie code wie den folgenden, um diese Methode aufzurufen:

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

Wäre der zurückgegebene Wert eine Struktur von weniger als 8 Bytes groß (wie die ältere SizeF , die vor dem Wechsel zu den einheitlichen APIs verwendet wurde), wäre der obige Code im Simulator ausgeführt worden, aber auf dem Gerät abgestürzt. Um einen Selektor aufzurufen, der einen Wert zurückgibt, der weniger als 8 Bits groß ist, deklarieren Sie die objc_msgSend_stret Funktion:

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

Verwenden Sie code wie den folgenden, um diese Methode aufzurufen:

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

Aufrufen eines Selektors

Das Aufrufen eines Selektors umfasst drei Schritte:

  1. Rufen Sie das Selektorziel ab.
  2. Rufen Sie den Selektornamen ab.
  3. Rufen Sie objc_msgSend mit den entsprechenden Argumenten auf.

Selektorziele

Ein Selektorziel ist entweder ein Objekt instance oder eine Objective-C Klasse. Wenn das Ziel ein instance ist und von einem gebundenen Xamarin.iOS-Typ stammt, verwenden Sie die ObjCRuntime.INativeObject.Handle -Eigenschaft.

Wenn das Ziel eine Klasse ist, verwenden Sie ObjCRuntime.Class , um einen Verweis auf die Klasse instance abzurufen, und verwenden Sie dann die Class.Handle -Eigenschaft.

Selektornamen

Selektornamen sind in der Apple-Dokumentation aufgeführt. Enthält sizeWithFont: z. NSString B. und sizeWithFont:forWidth:lineBreakMode: Selektoren. Die eingebetteten und nachfolgenden Doppelpunkte sind Teil des Selektornamens und können nicht weggelassen werden.

Sobald Sie über einen Selektornamen verfügen, können Sie eine ObjCRuntime.Selector instance dafür erstellen.

Aufrufen von objc_msgSend

objc_msgSend sendet eine Nachricht (Selektor) an ein -Objekt. Diese Funktionsfamilie akzeptiert mindestens zwei erforderliche Argumente: das Selektorziel (ein instance- oder Klassenhandle), den Selektor selbst und alle argumente, die für den Selektor erforderlich sind. Die argumente instance und selektor müssen seinSystem.IntPtr, und alle verbleibenden Argumente müssen mit dem Typ übereinstimmen, den der Selektor erwartet, z. B. ein nint für ein intoder ein System.IntPtr für alle NSObjectabgeleiteten Typen. Verwenden Sie die SchaltflächeNSObject.Handle-Eigenschaft zum Abrufen eines IntPtr für einen Objective-C Typ instance.

Es gibt mehrere objc_msgSend Funktionen:

  • Verwenden Sie objc_msgSend_stret für Selektoren, die eine Struktur zurückgeben. In ARM umfasst dies alle Rückgabetypen, die keine Enumeration sind, oder einen der integrierten C-Typen (char, , intshort, longfloat, , double). Auf x86 (dem Simulator) muss diese Methode für alle Strukturen verwendet werden, die größer als 8 Bytes sind (CGSize ist 8 Bytes und wird im Simulator nicht verwendet objc_msgSend_stret ).
  • Verwenden Sie nur objc_msgSend_fpret für Selektoren, die einen Gleitkommawert für x86 zurückgeben. Diese Funktion muss nicht für ARM verwendet werden. Verwenden Sie objc_msgSendstattdessen .
  • Die Standard objc_msgSend-Funktion wird für alle anderen Selektoren verwendet.

Nachdem Sie entschieden haben, welche objc_msgSend Funktionen Sie aufrufen müssen (Simulator und Gerät erfordern möglicherweise jeweils eine andere Methode), können Sie eine normale [DllImport] Methode verwenden, um die Funktion für den späteren Aufruf zu deklarieren.

Eine Reihe von vordefinierten objc_msgSend Deklarationen finden Sie in ObjCRuntime.Messaging.

Verschiedene Aufrufe auf Simulator und Gerät

Wie oben beschrieben, Objective-C verfügt über drei Arten von objc_msgSend Methoden: eine für reguläre Aufrufe, eine für Aufrufe, die Gleitkommawerte zurückgeben (nur x86), und eine für Aufrufe, die Strukturwerte zurückgeben. Letzteres enthält das Suffix _stret in ObjCRuntime.Messaging.

Wenn Sie eine Methode aufrufen, die bestimmte Strukturen (unten beschriebene Regeln) zurückgibt, müssen Sie die -Methode mit dem Rückgabewert als ersten Parameter als Wert out aufrufen:

// The following returns a PointF structure:
PointF ret;
Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, this.Handle, selConvertPointFromWindow.Handle, point, window.Handle);

Die Regel für die Verwendung der _stret_ -Methode unterscheidet sich bei x86 und ARM. Wenn Ihre Bindungen sowohl auf dem Simulator als auch auf dem Gerät funktionieren sollen, fügen Sie Code wie den folgenden hinzu:

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

Verwenden der objc_msgSend_stret-Methode

Verwenden Sie beim Erstellen für ARM dieobjc_msgSend_stretfür jeden Werttyp, der keine Enumeration ist, oder einen der Basistypen für eine Enumeration (int, , shortbyte, longdouble, , float).

Verwenden Sie beim Erstellen für x86objc_msgSend_stretfür jeden Werttyp, der keine Enumeration ist, oder einen der Basistypen für eine Enumeration (int, , shortbyte, long, doublefloat) und deren systemeigene Größe größer als 8 Byte ist.

Erstellen eigener Signaturen

Der folgende Gist kann verwendet werden, um bei Bedarf eigene Signaturen zu erstellen.