Partager via


Bibliothèques de liaisons Objective-C

Lorsque vous utilisez Xamarin.iOS ou Xamarin.Mac, vous pouvez rencontrer des cas où vous souhaitez consommer une bibliothèque tierce Objective-C . Dans ces situations, vous pouvez utiliser des projets de liaison Xamarin pour créer une liaison C# aux bibliothèques natives Objective-C . Le projet utilise les mêmes outils que ceux que nous utilisons pour apporter les API iOS et Mac à C#.

Ce document explique comment lier Objective-C des API, si vous liez uniquement des API C, vous devez utiliser le mécanisme .NET standard pour cela, l’infrastructure P/Invoke. Des détails sur la façon de lier statiquement une bibliothèque C sont disponibles dans la page Liaison de bibliothèques natives .

Consultez notre guide de référence sur les types de liaison complémentaire. En outre, si vous souhaitez en savoir plus sur ce qui se passe sous le capot, case activée notre page Vue d’ensemble de la liaison.

Les liaisons peuvent être générées pour les bibliothèques iOS et Mac. Cette page explique comment travailler sur une liaison iOS, mais les liaisons Mac sont très similaires.

Exemple de code pour iOS

Vous pouvez utiliser l’exemple de projet de liaison iOS pour expérimenter des liaisons.

Mise en route

Le moyen le plus simple de créer une liaison consiste à créer un projet de liaison Xamarin.iOS. Pour ce faire, vous pouvez effectuer cette opération à partir de Visual Studio pour Mac en sélectionnant le type de projet, bibliothèque de liaisons de bibliothèque > iOS > :

Effectuez cette opération à partir de Visual Studio pour Mac en sélectionnant le type de projet, bibliothèque de liaisons de bibliothèque iOS

Le projet généré contient un petit modèle que vous pouvez modifier, il contient deux fichiers : ApiDefinition.cs et StructsAndEnums.cs.

C’est ApiDefinition.cs là que vous allez définir le contrat d’API, il s’agit du fichier qui décrit comment l’API sous-jacente Objective-C est projetée en C#. La syntaxe et le contenu de ce fichier sont le sujet principal de la discussion de ce document et le contenu de celui-ci sont limités aux interfaces C# et aux déclarations de délégué C#. Le StructsAndEnums.cs fichier est le fichier dans lequel vous entrerez toutes les définitions requises par les interfaces et les délégués. Cela inclut des valeurs d’énumération et des structures que votre code peut utiliser.

Liaison d’une API

Pour effectuer une liaison complète, vous devez comprendre la définition de l’API Objective-C et vous familiariser avec les instructions de conception du .NET Framework.

Pour lier votre bibliothèque, vous commencerez généralement par un fichier de définition d’API. Un fichier de définition d’API est simplement un fichier source C# qui contient des interfaces C# qui ont été annotées avec quelques attributs qui aident à piloter la liaison. Ce fichier définit ce que le contrat entre C# et Objective-C c’est.

Par exemple, il s’agit d’un fichier API trivial pour une bibliothèque :

using Foundation;

namespace Cocos2D {
  [BaseType (typeof (NSObject))]
  interface Camera {
    [Static, Export ("getZEye")]
    nfloat ZEye { get; }

    [Export ("restore")]
    void Restore ();

    [Export ("locate")]
    void Locate ();

    [Export ("setEyeX:eyeY:eyeZ:")]
    void SetEyeXYZ (nfloat x, nfloat y, nfloat z);

    [Export ("setMode:")]
    void SetMode (CameraMode mode);
  }
}

L’exemple ci-dessus définit une classe appelée Cocos2D.Camera qui dérive du NSObject type de base (ce type provient) Foundation.NSObjectet qui définit une propriété statique (ZEye), deux méthodes qui ne prennent aucun argument et une méthode qui accepte trois arguments.

Une discussion approfondie du format du fichier API et des attributs que vous pouvez utiliser est abordée dans la section du fichier de définition d’API ci-dessous.

Pour produire une liaison complète, vous traiterez généralement quatre composants :

  • Fichier de définition d’API (ApiDefinition.cs dans le modèle).
  • Facultatif : énumérations, types, structs requis par le fichier de définition d’API (StructsAndEnums.cs dans le modèle).
  • Facultatif : sources supplémentaires qui peuvent développer la liaison générée ou fournir une API plus conviviale C# (tous les fichiers C# que vous ajoutez au projet).
  • Bibliothèque native que vous liez.

Ce graphique montre la relation entre les fichiers :

Ce graphique affiche la relation entre les fichiers

Le fichier définition d’API contient uniquement des espaces de noms et des définitions d’interface (avec tous les membres qu’une interface peut contenir) et ne doit pas contenir de classes, d’énumérations, de délégués ou de structs. Le fichier de définition d’API est simplement le contrat qui sera utilisé pour générer l’API.

Tout code supplémentaire dont vous avez besoin comme des énumérations ou des classes de prise en charge doit être hébergé sur un fichier distinct, dans l’exemple ci-dessus, « CameraMode » est une valeur d’énumération qui n’existe pas dans le fichier CS et qui doit être hébergée dans un fichier distinct, par exemple StructsAndEnums.cs:

public enum CameraMode {
    FlyOver, Back, Follow
}

Le APIDefinition.cs fichier est combiné à la StructsAndEnum classe et est utilisé pour générer la liaison principale de la bibliothèque. Vous pouvez utiliser la bibliothèque résultante telle quelle, mais en général, vous souhaiterez paramétrer la bibliothèque résultante pour ajouter certaines fonctionnalités C# à l’avantage de vos utilisateurs. Certains exemples incluent l’implémentation d’une ToString() méthode, fournir des indexeurs C#, ajouter des conversions implicites vers et à partir de certains types natifs ou fournir des versions fortement typées de certaines méthodes. Ces améliorations sont stockées dans des fichiers C# supplémentaires. Ajoutez simplement les fichiers C# à votre projet et ils seront inclus dans ce processus de génération.

Cela montre comment implémenter le code dans votre Extra.cs fichier. Notez que vous utiliserez des classes partielles, car celles-ci augmentent les classes partielles générées à partir de la combinaison de la liaison principale et de la ApiDefinition.csStructsAndEnums.cs liaison principale :

public partial class Camera {
    // Provide a ToString method
    public override string ToString ()
    {
         return String.Format ("ZEye: {0}", ZEye);
    }
}

La création de la bibliothèque produit votre liaison native.

Pour terminer cette liaison, vous devez ajouter la bibliothèque native au projet. Pour ce faire, vous pouvez ajouter la bibliothèque native à votre projet, soit en faisant glisser et en supprimant la bibliothèque native du Finder sur le projet dans l’Explorateur de solutions, soit en cliquant avec le bouton droit sur le projet et en choisissant Ajouter des>fichiers pour sélectionner la bibliothèque native. Les bibliothèques natives par convention commencent par le mot « lib » et se terminent par l’extension .a ». Lorsque vous effectuez cette opération, Visual Studio pour Mac ajoutera deux fichiers : le fichier .a et un fichier C# rempli automatiquement qui contient des informations sur ce que contient la bibliothèque native :

Les bibliothèques natives par convention commencent par le mot lib et se terminent par l’extension .a

Le contenu du fichier contient des informations sur la libMagicChord.linkwith.cs façon dont cette bibliothèque peut être utilisée et indique à votre IDE de empaqueter ce fichier binaire dans le fichier DLL résultant :

using System;
using ObjCRuntime;

[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]

Détails complets sur l’utilisation du [LinkWith]les attributs sont documentés dans le Guide de référence des types de liaison.

Maintenant, lorsque vous générez le projet, vous finirez par un MagicChords.dll fichier qui contient à la fois la liaison et la bibliothèque native. Vous pouvez distribuer ce projet ou la DLL résultante à d’autres développeurs pour leur propre utilisation.

Parfois, vous pouvez constater que vous avez besoin de quelques valeurs d’énumération, de définitions de délégués ou d’autres types. Ne placez pas ceux dans le fichier de définitions d’API, car il s’agit simplement d’un contrat

Fichier de définition d’API

Le fichier de définition d’API se compose d’un certain nombre d’interfaces. Les interfaces de la définition de l’API sont transformées en déclaration de classe et doivent être décorées avec l’attribut [BaseType] pour spécifier la classe de base pour la classe.

Vous vous demandez peut-être pourquoi nous n’avons pas utilisé de classes au lieu d’interfaces pour la définition de contrat. Nous avons choisi des interfaces parce qu’il nous a permis d’écrire le contrat pour une méthode sans avoir à fournir un corps de méthode dans le fichier de définition d’API, ou à fournir un corps qui devait lever une exception ou retourner une valeur significative.

Mais étant donné que nous utilisons l’interface comme squelette pour générer une classe, nous avons dû recourir à décorer différentes parties du contrat avec des attributs pour piloter la liaison.

Méthodes de liaison

La liaison la plus simple que vous pouvez faire consiste à lier une méthode. Déclarez simplement une méthode dans l’interface avec les conventions d’affectation de noms C# et décorez la méthode avec la [Export]Attribut . L’attribut [Export] est ce qui lie votre nom C# au Objective-C nom dans le runtime Xamarin.iOS. Paramètre du [Export] l’attribut est le nom du Objective-C sélecteur. Exemples :

// A method, that takes no arguments
[Export ("refresh")]
void Refresh ();

// A method that takes two arguments and return the result
[Export ("add:and:")]
nint Add (nint a, nint b);

// A method that takes a string
[Export ("draw:atColumn:andRow:")]
void Draw (string text, nint column, nint row);

Les exemples ci-dessus montrent comment lier des méthodes d’instance. Pour lier des méthodes statiques, vous devez utiliser l’attribut [Static] , comme suit :

// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();

Cela est nécessaire, car le contrat fait partie d’une interface, et les interfaces n’ont aucune notion de déclarations statiques et d’instances. Il est donc nécessaire une fois de plus de recourir à des attributs. Si vous souhaitez masquer une méthode particulière de la liaison, vous pouvez décorer la méthode avec l’attribut [Internal] .

La btouch-native commande introduit des case activée pour que les paramètres de référence ne soient pas null. Si vous souhaitez autoriser des valeurs Null pour un paramètre particulier, utilisez le [NullAllowed] attribut sur le paramètre, comme suit :

[Export ("setText:")]
string SetText ([NullAllowed] string text);

Lors de l’exportation d’un type de référence, avec le [Export] mot clé vous pouvez également spécifier la sémantique d’allocation. Cela est nécessaire pour s’assurer qu’aucune donnée n’est divulguée.

Propriétés de liaison

Tout comme les méthodes, Objective-C les propriétés sont liées à l’aide de la [Export] attribut et mappez directement aux propriétés C#. Tout comme les méthodes, les propriétés peuvent être décorées avec le [Static] et les [Internal] Attributs.

Lorsque vous utilisez l’attribut [Export] sur une propriété sous la couverture btouch-native lie en fait deux méthodes : getter et setter. Le nom que vous fournissez à exporter est le nom de base et le setter est calculé en préparant le mot « set », en transformant la première lettre du nom de base en majuscules et en faisant en sorte que le sélecteur prenne un argument. Cela signifie que l’application [Export ("label")] sur une propriété lie réellement les méthodes « label » et « setLabel : ». Objective-C

Parfois, les Objective-C propriétés ne suivent pas le modèle décrit ci-dessus et le nom est remplacé manuellement. Dans ces cas, vous pouvez contrôler la façon dont la liaison est générée à l’aide du [Bind] attribut sur le getter ou setter, par exemple :

[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }

Cela lie ensuite « isMenuVisible » et « setMenuVisible : ». Si vous le souhaitez, une propriété peut être liée à l’aide de la syntaxe suivante :

[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
  [Export ("name")]
  string Name();

  [Export("setName:")]
  void SetName(string name);
}

Où getter et setter sont explicitement définis comme dans les liaisons et setName les name liaisons ci-dessus.

Outre la prise en charge des propriétés statiques à l’aide [Static]de , vous pouvez décorer des propriétés statiques de thread avec [IsThreadStatic], par exemple :

[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }

Tout comme les méthodes, certains paramètres peuvent être marqués avec [NullAllowed], vous pouvez appliquer [NullAllowed] à une propriété pour indiquer que null est une valeur valide pour la propriété, par exemple :

[Export ("text"), NullAllowed]
string Text { get; set; }

Le [NullAllowed] paramètre peut également être spécifié directement sur le setter :

[Export ("text")]
string Text { get; [NullAllowed] set; }

Mises en garde des contrôles personnalisés de liaison

Les avertissements suivants doivent être pris en compte lors de la configuration de la liaison pour un contrôle personnalisé :

  1. Les propriétés de liaison doivent être statiques : lors de la définition de la liaison de propriétés, l’attribut [Static] doit être utilisé.
  2. Les noms de propriété doivent correspondre exactement : le nom utilisé pour lier la propriété doit correspondre exactement au nom de la propriété dans le contrôle personnalisé.
  3. Les types de propriétés doivent correspondre exactement : le type de variable utilisé pour lier la propriété doit correspondre au type de la propriété dans le contrôle personnalisé exactement.
  4. Points d’arrêt et getter/setter : les points d’arrêt placés dans les méthodes getter ou setter de la propriété ne seront jamais atteint.
  5. Observer les rappels : vous devez utiliser des rappels d’observation pour être informé des modifications apportées aux valeurs de propriété des contrôles personnalisés.

L’échec de l’observation de l’une des mises en garde répertoriées ci-dessus peut entraîner l’échec silencieux de la liaison au moment de l’exécution.

Objective-C modèle mutable et propriétés

Objective-C les frameworks utilisent une idiome où certaines classes sont immuables avec une sous-classe mutable. Par exemple NSString , la version immuable est NSMutableString la sous-classe qui autorise la mutation.

Dans ces classes, il est courant de voir la classe de base immuable contenir des propriétés avec un getter, mais pas de setter. Et pour que la version mutable introduise le setter. Comme cela n’est pas vraiment possible avec C#, nous avons dû mapper cet idiome dans un idiome qui fonctionnerait avec C#.

La façon dont cela est mappé à C# consiste à ajouter le getter et le setter sur la classe de base, mais à marquer le setter avec un setter [NotImplemented]Attribut .

Ensuite, sur la sous-classe mutable, vous utilisez le [Override] attribut sur la propriété pour vous assurer que la propriété est en fait en remplacement du comportement du parent.

Exemple :

[BaseType (typeof (NSObject))]
interface MyTree {
    string Name { get; [NotImplemented] set; }
}

[BaseType (typeof (MyTree))]
interface MyMutableTree {
    [Override]
    string Name { get; set; }
}

Constructeurs de liaison

L’outil btouch-native génère automatiquement quatre constructeurs dans votre classe, pour une classe Foodonnée, il génère :

  • Foo (): constructeur par défaut (mappe au Objective-Cconstructeur « init »)
  • Foo (NSCoder): le constructeur utilisé lors de la désérialisation des fichiers NIB (mappe au Objective-Cconstructeur « initWithCoder : ».
  • Foo (IntPtr handle): constructeur pour la création basée sur le handle, il est appelé par le runtime lorsque le runtime doit exposer un objet managé à partir d’un objet non managé.
  • Foo (NSEmptyFlag): ceci est utilisé par les classes dérivées pour empêcher l’initialisation double.

Pour les constructeurs que vous définissez, ils doivent être déclarés à l’aide de la signature suivante dans la définition de l’interface : ils doivent retourner une IntPtr valeur et le nom de la méthode doit être Constructeur. Par exemple, pour lier le initWithFrame: constructeur, il s’agit de ce que vous utiliseriez :

[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);

Protocoles de liaison

Comme décrit dans le document de conception d’API, dans la section traitant des modèles et des protocoles, Xamarin.iOS mappe les Objective-C protocoles en classes marquées par le indicateur [Model]Attribut . Cela est généralement utilisé lors de l’implémentation Objective-C de classes de délégué.

La grande différence entre une classe liée régulière et une classe déléguée est que la classe de délégué peut avoir une ou plusieurs méthodes facultatives.

Par exemple, considérez la UIKit classe UIAccelerometerDelegate, c’est comment elle est liée dans Xamarin.iOS :

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
        [Export ("accelerometer:didAccelerate:")]
        void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}

Étant donné qu’il s’agit d’une méthode facultative sur la définition, UIAccelerometerDelegate il n’y a rien d’autre à faire. Mais s’il existe une méthode requise sur le protocole, vous devez ajouter la [Abstract] attribut à la méthode. Cela force l’utilisateur de l’implémentation à fournir un corps pour la méthode.

En général, les protocoles sont utilisés dans les classes qui répondent aux messages. Cela est généralement effectué en Objective-C affectant à la propriété « délégué » une instance d’un objet qui répond aux méthodes du protocole.

La convention dans Xamarin.iOS consiste à prendre en charge à la fois le Objective-C style faiblement couplé où n’importe quelle instance d’un NSObject délégué peut être affectée et pour exposer également une version fortement typée de celui-ci. Pour cette raison, nous fournissons généralement à la fois une Delegate propriété fortement typée et un WeakDelegate qui est faiblement typé. Nous liez généralement la version faiblement typée avec [Export], et nous utilisons l’attribut [Wrap] pour fournir la version fortement typée.

Cela montre comment nous avons lié la UIAccelerometer classe :

[BaseType (typeof (NSObject))]
interface UIAccelerometer {
        [Static] [Export ("sharedAccelerometer")]
        UIAccelerometer SharedAccelerometer { get; }

        [Export ("updateInterval")]
        double UpdateInterval { get; set; }

        [Wrap ("WeakDelegate")]
        UIAccelerometerDelegate Delegate { get; set; }

        [Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
        NSObject WeakDelegate { get; set; }
}

Nouveautés de MonoTouch 7.0

À compter de MonoTouch 7.0, une fonctionnalité de liaison de protocole nouvelle et améliorée a été incorporée. Cette nouvelle prise en charge simplifie l’utilisation Objective-C des idiomes pour l’adoption d’un ou plusieurs protocoles dans une classe donnée.

Pour chaque définition MyProtocol de protocole, Objective-Cil existe désormais une IMyProtocol interface qui répertorie toutes les méthodes requises à partir du protocole, ainsi qu’une classe d’extension qui fournit toutes les méthodes facultatives. Les éléments ci-dessus, combinés avec une nouvelle prise en charge dans l’éditeur Xamarin Studio, permettent aux développeurs d’implémenter des méthodes de protocole sans avoir à utiliser les sous-classes distinctes des classes de modèle abstraites précédentes.

Toute définition qui contient l’attribut [Protocol] génère en fait trois classes de prise en charge qui améliorent considérablement la façon dont vous consommez des protocoles :

// Full method implementation, contains all methods
class MyProtocol : IMyProtocol {
    public void Say (string msg);
    public void Listen (string msg);
}

// Interface that contains only the required methods
interface IMyProtocol: INativeObject, IDisposable {
    [Export ("say:")]
    void Say (string msg);
}

// Extension methods
static class IMyProtocol_Extensions {
    public static void Optional (this IMyProtocol this, string msg);
    }
}

L’implémentation de classe fournit une classe abstraite complète que vous pouvez remplacer des méthodes individuelles et obtenir une sécurité de type complète. Toutefois, en raison de C# ne prenant pas en charge l’héritage multiple, il existe des scénarios où vous devrez peut-être avoir une autre classe de base, mais que vous souhaitez toujours implémenter une interface, c’est là que le

La définition d’interface générée est entrée. Il s’agit d’une interface qui a toutes les méthodes requises à partir du protocole. Cela permet aux développeurs qui souhaitent implémenter votre protocole pour simplement implémenter l’interface. Le runtime inscrit automatiquement le type lors de l’adoption du protocole.

Notez que l’interface répertorie uniquement les méthodes requises et expose les méthodes facultatives. Cela signifie que les classes qui adoptent le protocole obtiennent un type complet case activée ing pour les méthodes requises, mais doivent recourir à un type faible (manuellement à l’aide [Export] d’attributs et correspondant à la signature) pour les méthodes de protocole facultatives.

Pour qu’il soit pratique de consommer une API qui utilise des protocoles, l’outil de liaison produit également une classe de méthode d’extensions qui expose toutes les méthodes facultatives. Cela signifie que tant que vous consommez une API, vous pourrez traiter les protocoles comme ayant toutes les méthodes.

Si vous souhaitez utiliser les définitions de protocole dans votre API, vous devez écrire des interfaces vides squelettes dans votre définition d’API. Si vous souhaitez utiliser myProtocol dans une API, vous devez procéder comme suit :

[BaseType (typeof (NSObject))]
[Model, Protocol]
interface MyProtocol {
    // Use [Abstract] when the method is defined in the @required section
    // of the protocol definition in Objective-C
    [Abstract]
    [Export ("say:")]
    void Say (string msg);

    [Export ("listen")]
    void Listen ();
}

interface IMyProtocol {}

[BaseType (typeof(NSObject))]
interface MyTool {
    [Export ("getProtocol")]
    IMyProtocol GetProtocol ();
}

L’élément ci-dessus est nécessaire, car au moment de la liaison, il IMyProtocol n’existe pas, c’est pourquoi vous devez fournir une interface vide.

Adoption d’interfaces générées par le protocole

Chaque fois que vous implémentez l’une des interfaces générées pour les protocoles, comme suit :

class MyDelegate : NSObject, IUITableViewDelegate {
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

L’implémentation des méthodes d’interface requises est exportée avec le nom approprié. Il est donc équivalent à ceci :

class MyDelegate : NSObject, IUITableViewDelegate {
    [Export ("getRowHeight:")]
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

Cela fonctionnera pour tous les membres de protocole requis, mais il existe un cas particulier avec des sélecteurs facultatifs à connaître. Les membres de protocole facultatifs sont traités de façon identique lors de l’utilisation de la classe de base :

public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
	public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

mais lors de l’utilisation de l’interface de protocole, il est nécessaire d’ajouter le [Export]. L’IDE l’ajoute via la saisie semi-automatique lorsque vous l’ajoutez à partir d’un remplacement.

public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
	[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
	public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

Il existe une légère différence de comportement entre les deux lors de l’exécution.

  • Les utilisateurs de la classe de base (NSUrlSessionDownloadDelegate, par exemple) fournissent tous les sélecteurs obligatoires et facultatifs, retournant des valeurs par défaut raisonnables.
  • Les utilisateurs de l’interface (INSUrlSessionDownloadDelegate dans l’exemple) répondent uniquement aux sélecteurs exacts fournis.

Certaines classes rares peuvent se comporter différemment ici. Dans presque tous les cas cependant, il est sûr d’utiliser l’un ou l’autre.

Extensions de classe de liaison

Dans Objective-C ce cas, il est possible d’étendre des classes avec de nouvelles méthodes, similaires dans l’esprit aux méthodes d’extension C#. Lorsque l’une de ces méthodes est présente, vous pouvez utiliser la [BaseType] attribut pour marquer la méthode comme étant le récepteur du Objective-C message.

Par exemple, dans Xamarin.iOS, nous avons lié les méthodes d’extension qui sont définies quand NSStringUIKit elles sont importées en tant que méthodes dans le NSStringDrawingExtensions, comme suit :

[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
    [Export ("drawAtPoint:withFont:")]
    CGSize DrawString (CGPoint point, UIFont font);
}

Listes d’arguments de liaison Objective-C

Objective-C prend en charge les arguments variadiques. Par exemple :

- (void) appendWorkers:(XWorker *) firstWorker, ...
  NS_REQUIRES_NIL_TERMINATION ;

Pour appeler cette méthode à partir de C#, vous devez créer une signature comme suit :

[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)

Cela déclare la méthode comme interne, masquant l’API ci-dessus des utilisateurs, mais l’expose à la bibliothèque. Vous pouvez ensuite écrire une méthode comme suit :

public void AppendWorkers(params Worker[] workers)
{
    if (workers is null)
         throw new ArgumentNullException ("workers");

    var pNativeArr = Marshal.AllocHGlobal(workers.Length * IntPtr.Size);
    for (int i = 1; i < workers.Length; ++i)
        Marshal.WriteIntPtr (pNativeArr, (i - 1) * IntPtr.Size, workers[i].Handle);

    // Null termination
    Marshal.WriteIntPtr (pNativeArr, (workers.Length - 1) * IntPtr.Size, IntPtr.Zero);

    // the signature for this method has gone from (IntPtr, IntPtr) to (Worker, IntPtr)
    WorkerManager.AppendWorkers(workers[0], pNativeArr);
    Marshal.FreeHGlobal(pNativeArr);
}

Champs de liaison

Parfois, vous souhaiterez accéder aux champs publics qui ont été déclarés dans une bibliothèque.

En règle générale, ces champs contiennent des chaînes ou des valeurs entières qui doivent être référencées. Ils sont couramment utilisés comme chaîne qui représentent une notification spécifique et en tant que clés dans les dictionnaires.

Pour lier un champ, ajoutez une propriété à votre fichier de définition d’interface et décorez la propriété avec l’attribut [Field] . Cet attribut prend un paramètre : le nom C du symbole à rechercher. Par exemple :

[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }

Si vous souhaitez encapsuler différents champs dans une classe statique qui ne dérive NSObjectpas, vous pouvez utiliser le [Static] attribut sur la classe, comme suit :

[Static]
interface LonelyClass {
    [Field ("NSSomeEventNotification")]
    NSString NSSomeEventNotification { get; }
}

Le code ci-dessus génère un LonelyClass élément qui ne dérive NSObject pas et contient une liaison à l’exposé NSSomeEventNotificationNSString sous la forme d’un NSString.

L’attribut [Field] peut être appliqué aux types de données suivants :

  • NSString références (propriétés en lecture seule)
  • NSArray références (propriétés en lecture seule)
  • Ints 32 bits (System.Int32)
  • Ints 64 bits (System.Int64)
  • Floats 32 bits (System.Single)
  • Floats 64 bits (System.Double)
  • System.Drawing.SizeF
  • CGSize

En plus du nom de champ natif, vous pouvez spécifier le nom de la bibliothèque où se trouve le champ, en passant le nom de la bibliothèque :

[Static]
interface LonelyClass {
    [Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
    NSString SomeSharedLibrarySymbol { get; }
}

Si vous liez statiquement, il n’existe aucune bibliothèque à lier. Vous devez donc utiliser le __Internal nom :

[Static]
interface LonelyClass {
    [Field ("MyFieldFromALibrary", "__Internal")]
    NSString MyFieldFromALibrary { get; }
}

Énumérations de liaison

Vous pouvez ajouter enum directement vos fichiers de liaison pour faciliter leur utilisation dans les définitions d’API, sans utiliser de fichier source différent (qui doit être compilé à la fois dans les liaisons et le projet final).

Exemple :

[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}

interface MyType {
    [Export ("initWithEnum:")]
    IntPtr Constructor (MyEnum value);
}

Il est également possible de créer vos propres énumérations pour remplacer NSString les constantes. Dans ce cas, le générateur crée automatiquement les méthodes pour convertir les valeurs d’énumération et les constantes NSString pour vous.

Exemple :

enum NSRunLoopMode {

    [DefaultEnumValue]
    [Field ("NSDefaultRunLoopMode")]
    Default,

    [Field ("NSRunLoopCommonModes")]
    Common,

    [Field (null)]
    Other = 1000
}

interface MyType {
    [Export ("performForMode:")]
    void Perform (NSString mode);

    [Wrap ("Perform (mode.GetConstant ())")]
    void Perform (NSRunLoopMode mode);
}

Dans l’exemple ci-dessus, vous pouvez décider de décorer void Perform (NSString mode); avec un [Internal] attribut. Cela masque l’API basée sur des constantes de vos consommateurs de liaison.

Toutefois, cela limite la sous-classe du type, car l’alternative d’API nicer utilise un [Wrap] attribut. Ces méthodes générées ne sont pas virtual, c’est-à-dire que vous ne pourrez pas les remplacer , ce qui peut, ou non, être un bon choix.

Une alternative consiste à marquer la définition d’origine basée NSStringsur la définition [Protected]. Cela permet au sous-classement de fonctionner, le cas échéant, et la version encapsulée fonctionne toujours et appelle la méthode substituée.

Liaison NSValue, NSNumberet NSString à un meilleur type

L’attribut autorise la [BindAs] liaison NSNumberet NSValueNSString(enums) en types C# plus précis. L’attribut peut être utilisé pour créer une API .NET plus précise et plus précise sur l’API native.

Vous pouvez décorer des méthodes (sur valeur de retour), des paramètres et des propriétés avec [BindAs]. La seule restriction est que votre membre ne doit pas être à l’intérieur d’un [Protocol] ou [Model] interface.

Par exemple :

[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);

Sortie :

[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }

En interne, nous allons effectuer les bool?conversions ->NSNumber et CGRect<->NSValue .<

[BindAs] prend également en charge les tableaux de NSNumberNSValue et NSString(énumérations).

Par exemple :

[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }

Sortie :

[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }

CAScroll est une NSString énumération soutenue, nous allons extraire la valeur appropriée NSString et gérer la conversion de type.

Consultez la documentation pour afficher les [BindAs] types de conversion pris en charge.

Notifications de liaison

Les notifications sont des messages publiés dans l’application NSNotificationCenter.DefaultCenter et utilisés comme mécanisme pour diffuser des messages d’une partie de l’application à un autre. Les développeurs s’abonnent aux notifications en général à l’aide de la méthode AddObserver de NSNotificationCenter. Lorsqu’une application publie un message dans le centre de notification, elle contient généralement une charge utile stockée dans le dictionnaire NSNotification.UserInfo . Ce dictionnaire est faiblement typé et l’obtention d’informations hors de lui est sujette à des erreurs, car les utilisateurs doivent généralement lire dans la documentation sur les clés disponibles sur le dictionnaire et les types des valeurs qui peuvent être stockées dans le dictionnaire. La présence de clés est parfois utilisée en tant que booléen.

Le générateur de liaison Xamarin.iOS prend en charge les développeurs pour lier des notifications. Pour ce faire, vous définissez le [Notification] attribut sur une propriété qui a également été étiquetée avec un [Field] propriété (elle peut être publique ou privée).

Cet attribut peut être utilisé sans arguments pour les notifications qui ne comportent aucune charge utile, ou vous pouvez spécifier une System.Type autre interface dans la définition de l’API, généralement avec le nom se terminant par « EventArgs ». Le générateur transforme l’interface en classe qui sous-classe EventArgs et inclut toutes les propriétés répertoriées ici. L’attribut [Export] doit être utilisé dans la classe EventArgs pour répertorier le nom de la clé utilisée pour rechercher le Objective-C dictionnaire pour extraire la valeur.

Par exemple :

interface MyClass {
    [Notification]
    [Field ("MyClassDidStartNotification")]
    NSString DidStartNotification { get; }
}

Le code ci-dessus génère une classe MyClass.Notifications imbriquée avec les méthodes suivantes :

public class MyClass {
   [..]
   public Notifications {
      public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
   }
}

Les utilisateurs de votre code peuvent alors facilement s’abonner aux notifications publiées dans NSDefaultCenter à l’aide de code comme suit :

var token = MyClass.Notifications.ObserverDidStart ((notification) => {
    Console.WriteLine ("Observed the 'DidStart' event!");
});

La valeur retournée à partir de ObserveDidStart laquelle vous pouvez être utilisée pour arrêter facilement la réception de notifications, comme suit :

token.Dispose ();

Vous pouvez également appeler NSNotification.DefaultCenter.RemoveObserver et transmettre le jeton. Si votre notification contient des paramètres, vous devez spécifier une interface d’assistance EventArgs , comme suit :

interface MyClass {
    [Notification (typeof (MyScreenChangedEventArgs)]
    [Field ("MyClassScreenChangedNotification")]
    NSString ScreenChangedNotification { get; }
}

// The helper EventArgs declaration
interface MyScreenChangedEventArgs {
    [Export ("ScreenXKey")]
    nint ScreenX { get; set; }

    [Export ("ScreenYKey")]
    nint ScreenY { get; set; }

    [Export ("DidGoOffKey")]
    [ProbePresence]
    bool DidGoOff { get; }
}

La classe ci-dessus génère une MyScreenChangedEventArgs classe avec les ScreenXScreenY propriétés qui récupèrent les données du dictionnaire NSNotification.UserInfo à l’aide des clés « ScreenXKey » et « ScreenYKey » respectivement et appliquent les conversions appropriées. L’attribut [ProbePresence] est utilisé pour que le générateur sonde si la clé est définie dans le UserInfo, au lieu d’essayer d’extraire la valeur. Cela est utilisé pour les cas où la présence de la clé est la valeur (généralement pour les valeurs booléennes).

Cela vous permet d’écrire du code comme suit :

var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
    Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});

Catégories de liaisons

Les catégories sont un Objective-C mécanisme utilisé pour étendre l’ensemble de méthodes et de propriétés disponibles dans une classe. Dans la pratique, ils sont utilisés pour étendre les fonctionnalités d’une classe de base (par exemple NSObject) lorsqu’un framework spécifique est lié (par exemple UIKit), rendant leurs méthodes disponibles, mais uniquement si le nouveau framework est lié. Dans d’autres cas, ils sont utilisés pour organiser les fonctionnalités d’une classe par fonctionnalité. Ils sont similaires dans l’esprit aux méthodes d’extension C#. Voici à quoi ressemble une catégorie dans Objective-C:

@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end

L’exemple ci-dessus s’il se trouve sur une bibliothèque étendrait les instances de UIView la méthode makeBackgroundRed.

Pour les lier, vous pouvez utiliser l’attribut [Category] sur une définition d’interface. Lors de l’utilisation de l’objet [Category] attribut, signification du [BaseType] l’attribut change d’être utilisé pour spécifier la classe de base à étendre, pour être le type à étendre.

L’exemple suivant montre comment les UIView extensions sont liées et transformées en méthodes d’extension C# :

[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
    [Export ("makeBackgroundRed")]
    void MakeBackgroundRed ();
}

La classe ci-dessus crée une MyUIViewExtension classe qui contient la méthode d’extension MakeBackgroundRed . Cela signifie que vous pouvez maintenant appeler « MakeBackgroundRed » sur n’importe quelle UIView sous-classe, ce qui vous donne les mêmes fonctionnalités que celles que Objective-Cvous obtiendriez . Dans d’autres cas, les catégories ne sont pas utilisées pour étendre une classe système, mais pour organiser des fonctionnalités, purement à des fins de décoration. Comme ceci :

@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end

@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end

Bien que vous puissiez utiliser le [Category] attribut également pour ce style de décoration de déclarations, vous pouvez également simplement les ajouter à la définition de classe. Ces deux objectifs seraient identiques :

[BaseType (typeof (NSObject))]
interface SocialNetworking {
}

[Category]
[BaseType (typeof (SocialNetworking))]
interface Twitter {
    [Export ("postToTwitter:")]
    void PostToTwitter (Message message);
}

[Category]
[BaseType (typeof (SocialNetworking))]
interface Facebook {
    [Export ("postToFacebook:andPicture:")]
    void PostToFacebook (Message message, UIImage picture);
}

Il est juste plus court dans ces cas de fusionner les catégories :

[BaseType (typeof (NSObject))]
interface SocialNetworking {
    [Export ("postToTwitter:")]
    void PostToTwitter (Message message);

    [Export ("postToFacebook:andPicture:")]
    void PostToFacebook (Message message, UIImage picture);
}

Blocs de liaison

Les blocs sont une nouvelle construction introduite par Apple pour apporter l’équivalent fonctionnel des méthodes anonymes C# à Objective-C. Par exemple, la NSSet classe expose maintenant cette méthode :

- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block

La description ci-dessus déclare une méthode appelée enumerateObjectsUsingBlock: qui accepte un argument nommé block. Ce bloc est similaire à une méthode anonyme C# dans laquelle il prend en charge la capture de l’environnement actuel (le pointeur « this », accès aux variables et paramètres locaux). La méthode ci-dessus appelle NSSet le bloc avec deux paramètres un NSObject (le id obj composant) et un pointeur vers une partie booléenne (la BOOL *stoppartie).

Pour lier ce type d’API avec btouch, vous devez d’abord déclarer la signature de type de bloc en tant que délégué C#, puis la référencer à partir d’un point d’entrée d’API, comme suit :

// This declares the callback signature for the block:
delegate void NSSetEnumerator (NSObject obj, ref bool stop)

// Later, inside your definition, do this:
[Export ("enumerateObjectUsingBlock:")]
void Enumerate (NSSetEnumerator enum)

Et maintenant, votre code peut appeler votre fonction à partir de C# :

var myset = new NSMutableSet ();
myset.Add (new NSString ("Foo"));

s.Enumerate (delegate (NSObject obj, ref bool stop){
    Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});

Vous pouvez également utiliser des lambdas si vous préférez :

var myset = new NSMutableSet ();
mySet.Add (new NSString ("Foo"));

s.Enumerate ((obj, stop) => {
    Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});

Méthodes asynchrones

Le générateur de liaison peut transformer une certaine classe de méthodes en méthodes asynchrones (méthodes qui retournent une tâche ou une tâche<T>).

Vous pouvez utiliser [Async] attribut sur les méthodes qui retournent void et dont le dernier argument est un rappel. Lorsque vous appliquez cela à une méthode, le générateur de liaison génère une version de cette méthode avec le suffixe Async. Si le rappel ne prend aucun paramètre, la valeur de retour est un Task, si le rappel prend un paramètre, le résultat est un Task<T>. Si le rappel prend plusieurs paramètres, vous devez définir ou ResultTypeResultTypeName spécifier le nom souhaité du type généré qui contiendra toutes les propriétés.

Exemple :

[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);

Le code ci-dessus génère à la fois la méthode LoadFile, ainsi que :

[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);

Surfacing strong types for weak NSDictionary parameters

Dans de nombreux endroits de l’API Objective-C , les paramètres sont passés sous forme d’API faiblement typées NSDictionary avec des clés et des valeurs spécifiques, mais ils sont sujettes à des erreurs (vous pouvez passer des clés non valides et obtenir aucun avertissement ; vous pouvez transmettre des valeurs non valides et obtenir aucun avertissement) et frustrants à utiliser car ils nécessitent plusieurs voyages dans la documentation pour rechercher les noms et valeurs de clés possibles.

La solution consiste à fournir une version fortement typée qui fournit la version fortement typée de l’API et en arrière-plan mappe les différentes clés et valeurs sous-jacentes.

Par exemple, si l’API Objective-C a accepté une NSDictionary clé et qu’elle est documentée comme prenant la clé XyzVolumeKey qui prend une NSNumber valeur de volume comprise entre 0.0 et 1.0 et une XyzCaptionKey chaîne qui prend une chaîne, vous souhaitez que vos utilisateurs aient une API agréable qui ressemble à ceci :

public class  XyzOptions {
    public nfloat? Volume { get; set; }
    public string Caption { get; set; }
}

La Volume propriété est définie comme float nullable, car la convention dans Objective-C n’exige pas que ces dictionnaires aient la valeur. Il existe donc des scénarios où la valeur peut ne pas être définie.

Pour ce faire, vous devez effectuer quelques opérations :

  • Créez une classe fortement typée, qui sous-classe DictionaryContainer et fournit les différents getters et setters pour chaque propriété.
  • Déclarez des surcharges pour les méthodes prenant NSDictionary pour prendre la nouvelle version fortement typée.

Vous pouvez créer la classe fortement typée manuellement ou utiliser le générateur pour effectuer le travail pour vous. Nous explorons d’abord comment procéder manuellement afin de comprendre ce qui se passe, puis l’approche automatique.

Vous devez créer un fichier de prise en charge pour cela, il n’est pas entré dans votre API de contrat. Voici ce que vous devrez écrire pour créer votre classe XyzOptions :

public class XyzOptions : DictionaryContainer {
# if !COREBUILD
    public XyzOptions () : base (new NSMutableDictionary ()) {}
    public XyzOptions (NSDictionary dictionary) : base (dictionary){}

    public nfloat? Volume {
       get { return GetFloatValue (XyzOptionsKeys.VolumeKey); }
       set { SetNumberValue (XyzOptionsKeys.VolumeKey, value); }
    }
    public string Caption {
       get { return GetStringValue (XyzOptionsKeys.CaptionKey); }
       set { SetStringValue (XyzOptionsKeys.CaptionKey, value); }
    }
# endif
}

Vous devez ensuite fournir une méthode wrapper qui expose l’API de haut niveau, au-dessus de l’API de bas niveau.

[BaseType (typeof (NSObject))]
interface XyzPanel {
    [Export ("playback:withOptions:")]
    void Playback (string fileName, [NullAllowed] NSDictionary options);

    [Wrap ("Playback (fileName, options?.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

Si votre API n’a pas besoin d’être remplacée, vous pouvez masquer en toute sécurité l’API basée sur NSDictionary à l’aide de l’api [Internal]Attribut .

Comme vous pouvez le voir, nous utilisons le [Wrap] attribut pour faire apparaître un nouveau point d’entrée d’API et nous l’avons exposé à l’aide de notre classe fortement typée XyzOptions . La méthode wrapper autorise également la transmission de null.

Maintenant, une chose que nous n’avons pas mentionné est l’endroit où les XyzOptionsKeys valeurs proviennent. Vous devez généralement regrouper les clés qu’une API présente dans une classe statique, XyzOptionsKeyscomme suit :

[Static]
class XyzOptionKeys {
    [Field ("kXyzVolumeKey")]
    NSString VolumeKey { get; }

    [Field ("kXyzCaptionKey")]
    NSString CaptionKey { get; }
}

Examinons la prise en charge automatique de la création de ces dictionnaires fortement typés. Cela évite beaucoup de réutilisables, et vous pouvez définir le dictionnaire directement dans votre contrat d’API, au lieu d’utiliser un fichier externe.

Pour créer un dictionnaire fortement typé, introduisez une interface dans votre API et décorez-la avec l’attribut StrongDictionary . Cela indique au générateur qu’il doit créer une classe portant le même nom que votre interface qui dérivera DictionaryContainer et fournira des accesseurs typés forts pour celui-ci.

L’attribut [StrongDictionary] prend un paramètre, qui est le nom de la classe statique qui contient vos clés de dictionnaire. Ensuite, chaque propriété de l’interface devient un accesseur fortement typé. Par défaut, le code utilise le nom de la propriété avec le suffixe « Key » dans la classe statique pour créer l’accesseur.

Cela signifie que la création de votre accesseur fortement typé n’a plus besoin d’un fichier externe, ni d’avoir à créer manuellement des getters et des setters pour chaque propriété, ni à rechercher manuellement les clés manuellement.

Il s’agit de ce que votre liaison entière ressemblerait à ceci :

[Static]
class XyzOptionKeys {
    [Field ("kXyzVolumeKey")]
    NSString VolumeKey { get; }

    [Field ("kXyzCaptionKey")]
    NSString CaptionKey { get; }
}
[StrongDictionary ("XyzOptionKeys")]
interface XyzOptions {
    nfloat Volume { get; set; }
    string Caption { get; set; }
}

[BaseType (typeof (NSObject))]
interface XyzPanel {
    [Export ("playback:withOptions:")]
    void Playback (string fileName, [NullAllowed] NSDictionary options);

    [Wrap ("Playback (fileName, options?.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

Si vous devez référencer dans vos XyzOption membres un autre champ (qui n’est pas le nom de la propriété avec le suffixe Key), vous pouvez décorer la propriété avec un [Export] attribut portant le nom que vous souhaitez utiliser.

Mappages de types

Cette section explique comment Objective-C les types sont mappés aux types C#.

Types simples

Le tableau suivant montre comment mapper les types du Objective-C monde CocoaTouch au monde Xamarin.iOS :

Objective-C nom du type Type d’API unifiée Xamarin.iOS
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString (plus d’informations sur la liaison NSString) string
char * string (voir aussi : [PlainString])
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
Types CoreFoundation (CF*) CoreFoundation.CF*
GLint nint
GLfloat nfloat
Types de base (NS*) Foundation.NS*
id Foundation.NSObject
NSGlyph nint
NSSize CGSize
NSTextAlignment UITextAlignment
SEL ObjCRuntime.Selector
dispatch_queue_t CoreFoundation.DispatchQueue
CFTimeInterval double
CFIndex nint
NSGlyph nuint

Tableaux

Le runtime Xamarin.iOS s’occupe automatiquement de la conversion de tableaux C# vers NSArrays et d’effectuer la conversion en arrière, par exemple la méthode imaginaire Objective-C qui retourne un NSArray des UIViewséléments suivants :

// Get the peer views - untyped
- (NSArray *)getPeerViews ();

// Set the views for this container
- (void) setViews:(NSArray *) views

Est lié comme suit :

[Export ("getPeerViews")]
UIView [] GetPeerViews ();

[Export ("setViews:")]
void SetViews (UIView [] views);

L’idée est d’utiliser un tableau C# fortement typé, car cela permettra à l’IDE de fournir une saisie semi-automatique appropriée avec le type réel sans forcer l’utilisateur à deviner, ou à rechercher la documentation pour déterminer le type réel des objets contenus dans le tableau.

Dans les cas où vous ne pouvez pas suivre le type le plus dérivé réel contenu dans le tableau, vous pouvez utiliser NSObject [] comme valeur de retour.

Sélecteurs

Les sélecteurs apparaissent sur l’API Objective-C comme type SELspécial. Lors de la liaison d’un sélecteur, vous devez mapper le type à ObjCRuntime.Selector. En règle générale, les sélecteurs sont exposés dans une API avec à la fois un objet, l’objet cible et un sélecteur à appeler dans l’objet cible. Fournir ces deux éléments correspond essentiellement au délégué C# : quelque chose qui encapsule à la fois la méthode à appeler ainsi que l’objet dans lequel appeler la méthode.

Voici à quoi ressemble la liaison :

interface Button {
   [Export ("setTarget:selector:")]
   void SetTarget (NSObject target, Selector sel);
}

C’est ainsi que la méthode serait généralement utilisée dans une application :

class DialogPrint : UIViewController {
    void HookPrintButton (Button b)
    {
        b.SetTarget (this, new Selector ("print"));
    }

    [Export ("print")]
    void ThePrintMethod ()
    {
       // This does the printing
    }
}

Pour rendre la liaison plus agréable pour les développeurs C#, vous fournissez généralement une méthode qui accepte un NSAction paramètre, ce qui permet aux délégués C# et aux lambdas d’être utilisés au lieu du Target+Selector. Pour ce faire, vous masqueriez généralement la SetTarget méthode en la signalant avec un [Internal] attribut, puis vous exposeriez une nouvelle méthode d’assistance, comme suit :

// API.cs
interface Button {
   [Export ("setTarget:selector:"), Internal]
   void SetTarget (NSObject target, Selector sel);
}

// Extensions.cs
public partial class Button {
     public void SetTarget (NSAction callback)
     {
         SetTarget (new NSActionDispatcher (callback), NSActionDispatcher.Selector);
     }
}

Par conséquent, votre code utilisateur peut maintenant être écrit comme suit :

class DialogPrint : UIViewController {
    void HookPrintButton (Button b)
    {
        // First Style
        b.SetTarget (ThePrintMethod);

        // Lambda style
        b.SetTarget (() => {  /* print here */ });
    }

    void ThePrintMethod ()
    {
       // This does the printing
    }
}

Chaînes

Lorsque vous liez une méthode qui accepte un NSString, vous pouvez la remplacer par un type de chaîne C#, à la fois sur les types de retour et les paramètres.

Le seul cas où vous souhaiterez peut-être utiliser une NSString chaîne directement est celui où la chaîne est utilisée comme jeton. Pour plus d’informations sur les chaînes et NSString, lisez le document NSString design sur l’API.

Dans certains cas rares, une API peut exposer une chaîne de type C (char *) au lieu d’une Objective-C chaîne (NSString *). Dans ces cas, vous pouvez annoter le paramètre avec le [PlainString]Attribut .

paramètres out/ref

Certaines API retournent des valeurs dans leurs paramètres ou passent des paramètres par référence.

En règle générale, la signature ressemble à ceci :

- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref

Le premier exemple montre un idiome courant Objective-C pour retourner des codes d’erreur, un pointeur vers un NSError pointeur est passé et, lors du retour, la valeur est définie. La deuxième méthode montre comment une Objective-C méthode peut prendre un objet et modifier son contenu. Il s’agit d’un passage par référence, plutôt que d’une valeur de sortie pure.

Votre liaison se présente comme suit :

[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);

Attributs de gestion de la mémoire

Lorsque vous utilisez l’attribut [Export] et que vous transmettez des données qui seront conservées par la méthode appelée, vous pouvez spécifier la sémantique d’argument en la transmettant en tant que deuxième paramètre, par exemple :

[Export ("method", ArgumentSemantic.Retain)]

La valeur ci-dessus indique que la sémantique « Conserver » est définie. La sémantique disponible est la suivante :

  • Affecter
  • Copier
  • Conserver

Recommandations sur le style

Utilisation de [Interne]

Vous pouvez utiliser [Internal] attribut pour masquer une méthode de l’API publique. Vous pouvez le faire dans les cas où l’API exposée est trop basse et que vous souhaitez fournir une implémentation de haut niveau dans un fichier distinct basé sur cette méthode.

Vous pouvez également l’utiliser lorsque vous rencontrez des limitations dans le générateur de liaisons, par exemple certains scénarios avancés peuvent exposer des types qui ne sont pas liés et que vous souhaitez lier de votre propre manière, et vous souhaitez encapsuler ces types vous-même de votre propre manière.

Gestionnaires d’événements et rappels

Objective-C les classes diffusent généralement des notifications ou demandent des informations en envoyant un message sur une classe déléguée (Objective-C délégué).

Ce modèle, bien que entièrement pris en charge et exposé par Xamarin.iOS peut parfois être fastidieux. Xamarin.iOS expose le modèle d’événement C# et un système de rappel de méthode sur la classe qui peut être utilisé dans ces situations. Cela permet au code semblable à celui-ci d’exécuter :

button.Clicked += delegate {
    Console.WriteLine ("I was clicked");
};

Le générateur de liaisons est capable de réduire la quantité de saisie requise pour mapper le Objective-C modèle dans le modèle C#.

À compter de Xamarin.iOS 1.4, il est possible de demander au générateur de produire des liaisons pour un délégué spécifique Objective-C et d’exposer le délégué en tant qu’événements et propriétés C# sur le type d’hôte.

Il existe deux classes impliquées dans ce processus, la classe hôte qui émet actuellement des événements et les envoie à la Delegate classe déléguée ou WeakDelegate ou réelle.

Compte tenu de la configuration suivante :

[BaseType (typeof (NSObject))]
interface MyClass {
    [Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
    NSObject WeakDelegate { get; set; }

    [Wrap ("WeakDelegate")][NullAllowed]
    MyClassDelegate Delegate { get; set; }
}

[BaseType (typeof (NSObject))]
interface MyClassDelegate {
    [Export ("loaded:bytes:")]
    void Loaded (MyClass sender, int bytes);
}

Pour encapsuler la classe, vous devez :

  • Dans votre classe d’hôte, ajoutez-y [BaseType]
    déclare le type qui agit en tant que délégué et le nom C# que vous avez exposés. Dans notre exemple ci-dessus, il s’agit typeof (MyClassDelegate) et WeakDelegate respectivement.
  • Dans votre classe de délégué, sur chaque méthode qui a plus de deux paramètres, vous devez spécifier le type que vous souhaitez utiliser pour la classe EventArgs générée automatiquement.

Le générateur de liaisons n’est pas limité à l’habillage d’une seule destination d’événement, il est possible que certaines Objective-C classes émettent des messages à plusieurs délégués. Vous devrez donc fournir des tableaux pour prendre en charge cette configuration. La plupart des configurations n’en auront pas besoin, mais le générateur est prêt à prendre en charge ces cas.

Le code résultant sera :

[BaseType (typeof (NSObject),
    Delegates=new string [] {"WeakDelegate"},
    Events=new Type [] { typeof (MyClassDelegate) })]
interface MyClass {
        [Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
        NSObject WeakDelegate { get; set; }

        [Wrap ("WeakDelegate")][NullAllowed]
        MyClassDelegate Delegate { get; set; }
}

[BaseType (typeof (NSObject))]
interface MyClassDelegate {
        [Export ("loaded:bytes:"), EventArgs ("MyClassLoaded")]
        void Loaded (MyClass sender, int bytes);
}

Permet EventArgs de spécifier le nom de la EventArgs classe à générer. Vous devez utiliser une par signature (dans cet exemple, la EventArgs propriété contient une With propriété de type nint).

Avec les définitions ci-dessus, le générateur génère l’événement suivant dans la classe MyClass générée :

public MyClassLoadedEventArgs : EventArgs {
        public MyClassLoadedEventArgs (nint bytes);
        public nint Bytes { get; set; }
}

public event EventHandler<MyClassLoadedEventArgs> Loaded {
        add; remove;
}

Vous pouvez maintenant utiliser le code comme suit :

MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
        Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};

Les rappels sont tout comme les appels d’événements, la différence est qu’au lieu d’avoir plusieurs abonnés potentiels (par exemple, plusieurs méthodes peuvent se connecter à un événement ou à un ClickedDownloadFinished événement), les rappels ne peuvent avoir qu’un seul abonné.

Le processus est identique, la seule différence est qu’au lieu d’exposer le nom de la EventArgs classe qui sera générée, EventArgs est réellement utilisé pour nommer le nom du délégué C# résultant.

Si la méthode de la classe de délégué retourne une valeur, le générateur de liaison mappe cela en une méthode déléguée dans la classe parente au lieu d’un événement. Dans ces cas, vous devez fournir la valeur par défaut qui doit être retournée par la méthode si l’utilisateur ne se connecte pas au délégué. Pour ce faire, utilisez le [DefaultValue] ou [DefaultValueFromArgument] attributs.

[DefaultValue] codera en dur une valeur de retour, tandis que [DefaultValueFromArgument] est utilisé pour spécifier l’argument d’entrée qui sera retourné.

Énumérations et types de base

Vous pouvez également référencer des énumérations ou des types de base qui ne sont pas directement pris en charge par le système de définition d’interface btouch. Pour ce faire, placez vos énumérations et types principaux dans un fichier distinct et incluez-le dans le cadre de l’un des fichiers supplémentaires que vous fournissez à btouch.

Liaison des dépendances

Si vous êtes des API de liaison qui ne font pas partie de votre application, vous devez vous assurer que votre exécutable est lié à ces bibliothèques.

Vous devez informer Xamarin.iOS comment lier vos bibliothèques, soit en modifiant votre configuration de build pour appeler la mtouch commande avec des arguments de build supplémentaires qui spécifient comment établir un lien avec les nouvelles bibliothèques à l’aide de l’option « -gcc_flags », suivi d’une chaîne entre guillemets qui contient toutes les bibliothèques supplémentaires requises pour votre programme, Comme ça:

-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"

L’exemple ci-dessus lie libMyLibrary.aet libSystemLibrary.dylib la CFNetwork bibliothèque d’infrastructure à votre exécutable final.

Vous pouvez également tirer parti du niveau [LinkWithAttribute]assembly que vous pouvez incorporer dans vos fichiers de contrat (par AssemblyInfo.csexemple). Lorsque vous utilisez le [LinkWithAttribute]fichier , vous devez disposer de votre bibliothèque native au moment de la création de votre liaison, car celle-ci incorpore la bibliothèque native avec votre application. Par exemple :

// Specify only the library name as a constructor argument and specify everything else with properties:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget = LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]

// Or you can specify library name *and* link target as constructor arguments:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]

Vous vous demandez peut-être pourquoi avez-vous besoin -force_load d’une commande, et la raison est que l’indicateur -ObjC bien qu’il compile le code dans, il ne conserve pas les métadonnées nécessaires pour prendre en charge les catégories (l’éditeur de liens/compilateur les bandes d’élimination de code morte) dont vous avez besoin au moment de l’exécution pour Xamarin.iOS.

Références assistées

Certains objets temporaires, tels que les feuilles d’action et les zones d’alerte, sont fastidieux à suivre pour les développeurs et le générateur de liaison peut vous aider un peu ici.

Par exemple, si vous aviez une classe qui affichait un message et généré un Done événement, la façon traditionnelle de gérer cela serait :

class Demo {
    MessageBox box;

    void ShowError (string msg)
    {
        box = new MessageBox (msg);
        box.Done += { box = null; ... };
    }
}

Dans le scénario ci-dessus, le développeur doit conserver la référence à l’objet lui-même et fuiter ou effacer activement la référence pour la zone en soi. Pendant que le code de liaison, le générateur prend en charge le suivi de la référence pour vous et l’efface lorsqu’une méthode spéciale est appelée, le code ci-dessus devient alors :

class Demo {
    void ShowError (string msg)
    {
        var box = new MessageBox (msg);
        box.Done += { ... };
    }
}

Notez qu’il n’est plus nécessaire de conserver la variable dans une instance, qu’elle fonctionne avec une variable locale et qu’il n’est pas nécessaire d’effacer la référence lorsque l’objet meurt.

Pour tirer parti de cela, votre classe doit avoir une propriété Events définie dans la [BaseType] déclaration et également la KeepUntilRef variable définie sur le nom de la méthode appelée lorsque l’objet a terminé son travail, comme suit :

[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
    [Export ("show")]
    void Show (string message);
}

Héritage des protocoles

À partir de Xamarin.iOS v3.2, nous prenons en charge l’héritage des protocoles marqués avec la [Model] propriété. Cela est utile dans certains modèles d’API, comme dans l’endroit où MapKit le MKOverlay protocole hérite du MKAnnotation protocole, et est adopté par un certain nombre de classes qui héritent de NSObject.

Historiquement, nous avons requis la copie du protocole dans chaque implémentation, mais dans ces cas, nous pouvons maintenant avoir la MKShape classe hériter du MKOverlay protocole et elle générera automatiquement toutes les méthodes requises.