Objective-C sélecteurs dans Xamarin.iOS

La Objective-C langue est basée sur des sélecteurs. Un sélecteur est un message qui peut être envoyé à un objet ou à une classe. Xamarin.iOS mappe instance sélecteurs aux méthodes instance, et les sélecteurs de classe aux méthodes statiques.

Contrairement aux fonctions C normales (et comme les fonctions membres C++), vous ne pouvez pas appeler directement un sélecteur à l’aide de P/Invoke À la place, les sélecteurs sont envoyés à une Objective-C classe ou instance à l’aide duobjc_msgSend Fonction.

Pour plus d’informations sur les messages dans Objective-C, consultez le guide Utilisation des objets d’Apple.

Exemple

Supposons que vous souhaitiez appeler lesizeWithFont:forWidth:lineBreakMode: sélecteur sur NSString. La déclaration (issue de la documentation d’Apple) est la suivante :

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

Cette API présente les caractéristiques suivantes :

  • Le type de retour est CGSize pour l’API unifiée.
  • Le font paramètre est un UIFont (et un type (indirectement) dérivé de NSObject, et est mappé à System.IntPtr.
  • Le width paramètre , CGFloat, est mappé à nfloat.
  • Le lineBreakMode paramètre , UILineBreakMode, a déjà été lié dans Xamarin.iOS en tant queUILineBreakMode Énumération.

En mettant tout cela ensemble, la objc_msgSend déclaration doit correspondre à :

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

Déclarez-le comme suit :

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

Pour appeler cette méthode, utilisez le code suivant :

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 la valeur retournée était une structure dont la taille était inférieure à 8 octets (comme l’ancienne SizeF utilisée avant de basculer vers les API unifiées), le code ci-dessus aurait été exécuté sur le simulateur, mais s’est bloqué sur l’appareil. Pour appeler un sélecteur qui retourne une valeur inférieure à 8 bits, déclarez la objc_msgSend_stret fonction :

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

Pour appeler cette méthode, utilisez le code suivant :

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

Appel d’un sélecteur

L’appel d’un sélecteur se fait en trois étapes :

  1. Obtenez la cible du sélecteur.
  2. Obtenez le nom du sélecteur.
  3. Appelez objc_msgSend avec les arguments appropriés.

Cibles de sélecteur

Une cible de sélecteur est un objet instance ou une Objective-C classe. Si la cible est un instance et provient d’un type Xamarin.iOS lié, utilisez la ObjCRuntime.INativeObject.Handle propriété .

Si la cible est une classe, utilisez ObjCRuntime.Class pour obtenir une référence à la classe instance, puis utilisez la Class.Handle propriété .

Noms des sélecteurs

Les noms des sélecteurs sont répertoriés dans la documentation d’Apple. Par exemple, NSString inclut sizeWithFont: les sélecteurs et sizeWithFont:forWidth:lineBreakMode: . Les deux-points incorporés et de fin font partie du nom du sélecteur et ne peuvent pas être omis.

Une fois que vous avez un nom de sélecteur, vous pouvez créer un ObjCRuntime.Selector instance pour celui-ci.

Appel de objc_msgSend

objc_msgSend envoie un message (sélecteur) à un objet . Cette famille de fonctions prend au moins deux arguments requis : la cible du sélecteur (un instance ou un handle de classe), le sélecteur lui-même et tous les arguments requis pour le sélecteur. Les arguments instance et sélecteur doivent être System.IntPtr, et tous les arguments restants doivent correspondre au type attendu par le sélecteur, par exemple un nint pour un intou un System.IntPtr pour tous les NSObjecttypes dérivés. Utilisez leNSObject.Handlepour obtenir un IntPtr pour un Objective-C type instance.

Il existe plusieurs objc_msgSend fonctions :

  • Utilisez objc_msgSend_stret pour les sélecteurs qui retournent un struct. Sur ARM, cela inclut tous les types de retour qui ne sont pas une énumération ou l’un des types intégrés C (char, short, int, long, float, ). double Sur x86 (le simulateur), cette méthode doit être utilisée pour toutes les structures d’une taille supérieure à 8 octets (CGSize est de 8 octets et n’est pas utilisée objc_msgSend_stret dans le simulateur).
  • Utilisez objc_msgSend_fpret pour les sélecteurs qui retournent une valeur à virgule flottante sur x86 uniquement. Cette fonction n’a pas besoin d’être utilisée sur ARM ; à la place, utilisez objc_msgSend.
  • La fonction objc_msgSend main est utilisée pour tous les autres sélecteurs.

Une fois que vous avez décidé objc_msgSend quelle(s) fonction(s) vous devez appeler (le simulateur et l’appareil peuvent chacun nécessiter une méthode différente), vous pouvez utiliser une méthode normale [DllImport] pour déclarer la fonction pour un appel ultérieur.

Vous trouverez un ensemble de déclarations prédéfinis objc_msgSend dans ObjCRuntime.Messaging.

Différents appels sur le simulateur et l’appareil

Comme décrit ci-dessus, Objective-C a trois types de objc_msgSend méthodes : une pour les appels réguliers, une pour les appels qui retournent des valeurs à virgule flottante (x86 uniquement) et une pour les appels qui retournent des valeurs de struct. Ce dernier inclut le suffixe _stret dans ObjCRuntime.Messaging.

Si vous appelez une méthode qui retourne certains structs (règles décrites ci-dessous), vous devez appeler la méthode avec la valeur de retour comme premier paramètre comme out valeur :

// 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 règle d’utilisation de la _stret_ méthode diffère sur x86 et ARM. Si vous souhaitez que vos liaisons fonctionnent à la fois sur le simulateur et sur l’appareil, ajoutez du code comme suit :

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

Utilisation de la méthode objc_msgSend_stret

Lors de la génération pour ARM, utilisez leobjc_msgSend_stret pour tout type valeur qui n’est pas une énumération ou l’un des types de base d’une énumération (int, byte, short, long, double, float).

Lors de la génération pour x86, utilisezobjc_msgSend_stretpour tout type valeur qui n’est pas une énumération ou l’un des types de base d’une énumération (int, byte, short, longdouble, , float) et dont la taille native est supérieure à 8 octets.

Création de vos propres signatures

Le gist suivant peut être utilisé pour créer vos propres signatures, si nécessaire.