Conception de l’API Xamarin.iOS

En plus des bibliothèques de classes de base qui font partie de Mono, Xamarin.iOS est fourni avec des liaisons pour différentes API iOS afin de permettre aux développeurs de créer des applications iOS natives avec Mono.

Au cœur de Xamarin.iOS, il existe un moteur d’interopérabilité qui établit un pont entre le monde C# et le Objective-C monde, ainsi que des liaisons pour les API basées sur C iOS comme CoreGraphics et OpenGL ES.

Le runtime de bas niveau pour communiquer avec Objective-C le code se trouve dans MonoTouch.ObjCRuntime. En outre, des liaisons pour Foundation, CoreFoundation et UIKit sont fournies.

Principes de conception

Cette section détaille certains de nos principes de conception pour les liaisons Xamarin.iOS (ils s’appliquent également à Xamarin.Mac, les liaisons Mono pour Objective-C sur macOS) :

  • Suivez les instructions de conception du framework

  • Autoriser les développeurs à sous-classer Objective-C les classes :

    • Dériver d’une classe existante
    • Appeler le constructeur de base à chaîne
    • Le remplacement des méthodes doit être effectué avec le système de substitution de C#
    • La sous-classe doit fonctionner avec des constructions standard C#
  • Ne pas exposer les développeurs aux Objective-C sélecteurs

  • Fournir un mécanisme pour appeler des bibliothèques arbitraires Objective-C

  • Rendre les tâches courantes Objective-C faciles et les tâches difficiles Objective-C possibles

  • Exposer Objective-C des propriétés en tant que propriétés C#

  • Exposez une API fortement typée :

    • Augmenter la sécurité du type
    • Réduire les erreurs d’exécution
    • Obtenir l’IDE IntelliSense sur les types de retour
    • Autorise la documentation contextuelle de l’IDE
  • Encouragez l’exploration dans l’IDE des API :

    • Par exemple, au lieu d’exposer un tableau faiblement typé, comme suit :

      NSArray *getViews
      

      Exposez un type fort, comme suit :

      NSView [] Views { get; set; }
      

      L’utilisation de types forts donne Visual Studio pour Mac la possibilité d’effectuer une autocomplétion lors de System.Array la navigation dans l’API, rend toutes les opérations disponibles sur la valeur retournée et permet à la valeur de retour de participer à LINQ.

  • Types C# natifs :

    • NSString Devient string

    • Transformer int les paramètres et uint qui auraient dû être des énumérations C# et des énumérations C# avec [Flags] des attributs

    • Au lieu d’objets neutres NSArray du type, exposez les tableaux sous forme de tableaux fortement typés.

    • Pour les événements et les notifications, donnez aux utilisateurs le choix entre :

      • Version fortement typée par défaut
      • Version faiblement typée pour les cas d’usage avancés
  • Prise en charge du Objective-C modèle de délégué :

    • Système d’événements C#
    • Exposer des délégués C# (lambdas, méthodes anonymes et System.Delegate) à Objective-C des API sous forme de blocs

Assemblys

Xamarin.iOS inclut de nombreux assemblys qui constituent le profil Xamarin.iOS. La page Assemblys contient plus d’informations.

Espaces de noms principaux

ObjCRuntime

L’espace de noms ObjCRuntime permet aux développeurs de relier les mondes entre C# et Objective-C. Il s’agit d’une nouvelle liaison, conçue spécifiquement pour iOS, basée sur l’expérience de Cocoa# et Gtk#.

Foundation

L’espace de noms Foundation fournit les types de données de base conçus pour interagir avec l’infrastructure Objective-C Foundation qui fait partie d’iOS et qui est la base de la programmation orientée objet dans Objective-C.

Xamarin.iOS met en miroir en C# la hiérarchie des classes de Objective-C. Par exemple, la Objective-C classe de base NSObject est utilisable à partir de C# via Foundation.NSObject.

Bien que l’espace de noms Foundation fournisse des liaisons pour les types Foundation sous-jacents Objective-C , dans quelques cas, nous avons mappé les types sous-jacents aux types .NET. Par exemple :

  • Au lieu de traiter NSString et NSArray, le runtime les expose en tant que chaînesC# et tableauxfortement typés dans l’ensemble de l’API.

  • Différentes API d’assistance sont exposées ici pour permettre aux développeurs de lier des API tierces Objective-C , d’autres API iOS ou des API qui ne sont pas actuellement liées par Xamarin.iOS.

Pour plus d’informations sur les API de liaison, consultez la section Générateur de liaison Xamarin.iOS .

NSObject

Le type NSObject est la base de toutes les Objective-C liaisons. Les types Xamarin.iOS miroir deux classes de types des API CocoaTouch iOS : les types C (généralement appelés types CoreFoundation) et les Objective-C types (qui dérivent tous de la classe NSObject).

Pour chaque type qui reflète un type non managé, il est possible d’obtenir l’objet natif via la propriété Handle .

Alors que Mono fournit le garbage collection pour tous vos objets, le Foundation.NSObject implémente l’interface System.IDisposable . Vous pouvez libérer explicitement les ressources d’un NSObject donné sans avoir à attendre que le garbage collector démarre. Il est important de libérer explicitement des ressources lorsque vous utilisez des NSObjects lourds, par exemple des UIImages qui peuvent contenir des pointeurs vers de grands blocs de données.

Si votre type doit effectuer une finalisation déterministe, remplacez la méthode NSObject.Dispose(bool) Le paramètre de Dispose est « suppression bool », et si la valeur est true, cela signifie que votre méthode Dispose est appelée, car l’utilisateur a explicitement appelé Dispose () sur l’objet . Une valeur false signifie que votre méthode Dispose(bool disposing) est appelée à partir du finaliseur sur le thread du finaliseur.

Catégories

À partir de Xamarin.iOS 8.10, il est possible de créer Objective-C des catégories à partir de C#.

Cette opération est effectuée à l’aide de l’attribut Category , en spécifiant le type à étendre en tant qu’argument à l’attribut . L’exemple suivant étend instance NSString.

[Category (typeof (NSString))]

Chaque méthode de catégorie utilise le mécanisme normal pour exporter des méthodes vers l’attribut Objective-CExport :

[Export ("today")]
public static string Today ()
{
    return "Today";
}

Toutes les méthodes d’extension managée doivent être statiques, mais il est possible de créer Objective-C des méthodes instance à l’aide de la syntaxe standard pour les méthodes d’extension en C# :

[Export ("toUpper")]
public static string ToUpper (this NSString self)
{
    return self.ToString ().ToUpper ();
}

et le premier argument de la méthode d’extension sera le instance sur lequel la méthode a été appelée.

Exemple complet :

[Category (typeof (NSString))]
public static class MyStringCategory
{
    [Export ("toUpper")]
    static string ToUpper (this NSString self)
    {
        return self.ToString ().ToUpper ();
    }
}

Cet exemple ajoute une méthode native toUpper instance à la classe NSString, qui peut être appelée à partir de Objective-C.

[Category (typeof (UIViewController))]
public static class MyViewControllerCategory
{
    [Export ("shouldAutoRotate")]
    static bool GlobalRotate ()
    {
        return true;
    }
}

Un scénario où cela est utile est l’ajout d’une méthode à un ensemble entier de classes dans votre codebase, par exemple, cela ferait en sorte que toutes les UIViewController instances signalent qu’elles peuvent faire pivoter :

[Category (typeof (UINavigationController))]
class Rotation_IOS6 {
      [Export ("shouldAutorotate:")]
      static bool ShouldAutoRotate (this UINavigationController self)
      {
          return true;
      }
}
PreserveAttribute

PreserveAttribute est un attribut personnalisé qui est utilisé pour indiquer à mtouch , l’outil de déploiement Xamarin.iOS, de conserver un type ou un membre d’un type pendant la phase de traitement de l’application afin de réduire sa taille.

Chaque membre qui n’est pas lié de manière statique par l’application est soumis à suppression. Par conséquent, cet attribut est utilisé pour marquer les membres qui ne sont pas référencés statiquement, mais qui sont toujours nécessaires à votre application.

Par exemple, si vous instanciez dynamiquement des types, vous souhaiterez éventuellement conserver le constructeur par défaut de vos types. Si vous utilisez la sérialisation XML, vous souhaiterez éventuellement conserver les propriétés de vos types.

Vous pouvez appliquer cet attribut à chaque membre d’un type ou au type lui-même. Si vous souhaitez conserver le type entier, vous pouvez utiliser la syntaxe [Preserve (AllMembers = true)] sur le type.

UIKit

L’espace de noms UIKit contient un mappage un-à-un à tous les composants d’interface utilisateur qui composent CocoaTouch sous la forme de classes C#. L’API a été modifiée pour suivre les conventions utilisées dans le langage C#.

Des délégués C# sont fournis pour les opérations courantes. Pour plus d’informations, consultez la section délégués .

OpenGLES

Pour OpenGLES, nous distribuons une version modifiée de l’API OpenTK , une liaison orientée objet à OpenGL qui a été modifiée pour utiliser des structures et des types de données CoreGraphics, et qui expose uniquement les fonctionnalités disponibles sur iOS.

La fonctionnalité OpenGLES 1.1 est disponible via le type ES11.GL.

La fonctionnalité OpenGLES 2.0 est disponible via le type ES20.GL.

La fonctionnalité OpenGLES 3.0 est disponible via le type ES30.GL.

Conception de liaison

Xamarin.iOS n’est pas simplement une liaison à la plateforme sous-jacente Objective-C . Il étend le système de type .NET et répartit le système pour mieux mélanger C# et Objective-C.

Tout comme P/Invoke est un outil utile pour appeler des bibliothèques natives sur Windows et Linux, ou comme la prise en charge d’IJW peut être utilisée pour COM Interop sur Windows, Xamarin.iOS étend le runtime pour prendre en charge la liaison d’objets C# à Objective-C des objets.

La discussion dans les sections suivantes n’est pas nécessaire pour les utilisateurs qui créent des applications Xamarin.iOS, mais aidera les développeurs à comprendre comment les choses sont effectuées et les aidera lors de la création d’applications plus complexes.

Types

Là où cela était logique, les types C# sont exposés à l’univers C# plutôt qu’aux types de base de bas niveau. Cela signifie que l’API utilise le type « string » C# au lieu de NSString et utilise des tableaux C# fortement typés au lieu d’exposer NSArray.

En général, dans la conception Xamarin.iOS et Xamarin.Mac, l’objet sous-jacent NSArray n’est pas exposé. Au lieu de cela, le runtime convertit NSArrayautomatiquement s en tableaux fortement typés d’une NSObject classe. Ainsi, Xamarin.iOS n’expose pas une méthode faiblement typée comme GetViews pour retourner un NSArray :

NSArray GetViews ();

Au lieu de cela, la liaison expose une valeur de retour fortement typée, comme suit :

UIView [] GetViews ();

Il existe quelques méthodes exposées dans NSArray, pour les cas d’angle où vous pouvez utiliser un directement, mais leur utilisation est déconseillée dans la liaison d’API NSArray .

En outre, dans l’API classique au lieu d’exposer CGRect, CGPointet CGSize à partir de l’API CoreGraphics, nous les avons remplacés par les System.Drawing implémentations RectangleF, PointF, et SizeF car elles aideraient les développeurs à conserver le code OpenGL existant qui utilise OpenTK. Lors de l’utilisation de la nouvelle API unifiée 64 bits, l’API CoreGraphics doit être utilisée.

Héritage

La conception de l’API Xamarin.iOS permet aux développeurs d’étendre les types natifs Objective-C de la même façon qu’ils étendraient un type C#, en utilisant le mot clé « override » sur une classe dérivée et en chaînant à l’implémentation de base à l’aide de l’mot clé C# « base ».

Cette conception permet aux développeurs d’éviter de traiter des Objective-C sélecteurs dans le cadre de leur processus de développement, car l’ensemble Objective-C du système est déjà encapsulé dans les bibliothèques Xamarin.iOS.

Générateur de types et d’interface

Lorsque vous créez des classes .NET qui sont des instances de types créés par Interface Builder, vous devez fournir un constructeur qui prend un seul IntPtr paramètre. Cela est nécessaire pour lier l’objet managé instance à l’objet non managé. Le code se compose d’une seule ligne, comme suit :

public partial class void MyView : UIView {
   // This is the constructor that you need to add.
   public MyView (IntPtr handle) : base (handle) {}
}

Délégués

Objective-C et C# ont des significations différentes pour le mot délégué dans chaque langue.

Dans le Objective-C monde, et dans la documentation que vous trouverez en ligne sur CocoaTouch, un délégué est généralement un instance d’une classe qui répond à un ensemble de méthodes. Cela est similaire à une interface C#, à la différence près que les méthodes ne sont pas toujours obligatoires.

Ces délégués jouent un rôle important dans UIKit et d’autres API CocoaTouch. Ils sont utilisés pour accomplir diverses tâches :

  • Pour fournir des notifications à votre code (similaire à la remise d’événements en C# ou Gtk+).
  • Pour implémenter des modèles pour les contrôles de visualisation des données.
  • Pour piloter le comportement d’un contrôle.

Le modèle de programmation a été conçu pour réduire la création de classes dérivées afin de modifier le comportement d’un contrôle. Cette solution est similaire dans l’esprit à ce que d’autres boîtes à outils d’interface utilisateur utilisateur ont fait au fil des ans : signaux de Gtk, emplacements Qt, événements Winforms, événements WPF/Silverlight, etc. Pour éviter d’avoir des centaines d’interfaces (une pour chaque action) ou d’obliger les développeurs à implémenter trop de méthodes dont ils n’ont pas besoin, Objective-C prend en charge les définitions de méthodes facultatives. Cela diffère des interfaces C# qui nécessitent l’implémentation de toutes les méthodes.

Dans Objective-C les classes, vous verrez que les classes qui utilisent ce modèle de programmation exposent une propriété, appelée delegate, qui est nécessaire pour implémenter les parties obligatoires de l’interface et zéro, ou plus, des parties facultatives.

Dans Xamarin.iOS, trois mécanismes mutuellement exclusifs pour lier ces délégués sont proposés :

  1. Via des événements.
  2. Fortement typé via une Delegate propriété
  3. Faiblement typé via une WeakDelegate propriété

Par exemple, considérez la classe UIWebView. Cette opération est distribuée à un instance UIWebViewDelegate, qui est affecté à la propriété de délégué.

Via les événements

Pour de nombreux types, Xamarin.iOS crée automatiquement un délégué approprié, qui transfère les UIWebViewDelegate appels aux événements C#. Pour UIWebView:

Par exemple, ce programme simple enregistre les heures de début et de fin lors du chargement d’une vue web :

DateTime startTime, endTime;
var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.LoadStarted += (o, e) => startTime = DateTime.Now;
web.LoadFinished += (o, e) => endTime = DateTime.Now;
Via Propriétés

Les événements sont utiles lorsqu’il peut y avoir plusieurs abonnés à l’événement. En outre, les événements sont limités aux cas où il n’existe aucune valeur de retour du code.

Dans les cas où le code est censé retourner une valeur, nous avons choisi plutôt les propriétés. Cela signifie qu’une seule méthode peut être définie à un moment donné dans un objet .

Par exemple, vous pouvez utiliser ce mécanisme pour fermer le clavier à l’écran sur le gestionnaire d’un UITextField:

void SetupTextField (UITextField tf)
{
    tf.ShouldReturn = delegate (textfield) {
        textfield.ResignFirstResponder ();
        return true;
    }
}

Dans UITextFieldce cas, la propriété de ShouldReturn prend comme argument un délégué qui retourne une valeur bool et détermine si textField doit faire quelque chose avec le bouton Retour enfoncé. Dans notre méthode, nous revenons true à l’appelant, mais nous supprimons également le clavier de l’écran (cela se produit lorsque le champ de texte appelle ResignFirstResponder).

Fortement typé via une propriété delegate

Si vous préférez ne pas utiliser d’événements, vous pouvez fournir votre propre sous-classe UIWebViewDelegate et l’affecter à la propriété UIWebView.Delegate . Une fois UIWebView.Delegate attribué, le mécanisme de répartition des événements UIWebView ne fonctionne plus et les méthodes UIWebViewDelegate sont appelées lorsque les événements correspondants se produisent.

Par exemple, ce type simple enregistre le temps nécessaire au chargement d’une vue web :

class Notifier : UIWebViewDelegate  {
    DateTime startTime, endTime;

    public override LoadStarted (UIWebView webview)
    {
        startTime = DateTime.Now;
    }

    public override LoadingFinished (UIWebView webView)
    {
        endTime= DateTime.Now;
    }
}

Le code ci-dessus est utilisé comme suit :

var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.Delegate = new Notifier ();

L’élément ci-dessus crée un UIWebViewer et lui indique d’envoyer des messages à un instance de Notifier, classe que nous avons créée pour répondre aux messages.

Ce modèle est également utilisé pour contrôler le comportement de certains contrôles, par exemple, dans le cas UIWebView, la propriété UIWebView.ShouldStartLoad permet UIWebView au instance de contrôler si le UIWebView va charger une page ou non.

Le modèle est également utilisé pour fournir les données à la demande pour quelques contrôles. Par exemple, le contrôle UITableView est un contrôle de rendu de table puissant, et l’apparence et le contenu sont pilotés par un instance d’un UITableViewDataSource

Faiblement typé via la propriété WeakDelegate

En plus de la propriété fortement typée, il existe également un délégué typé faible qui permet au développeur de lier les choses différemment si vous le souhaitez. Partout où une propriété fortement typée Delegate est exposée dans la liaison de Xamarin.iOS, une propriété correspondante WeakDelegate est également exposée.

Lors de l’utilisation de WeakDelegate, il vous incombe de décorer correctement votre classe à l’aide de l’attribut Export pour spécifier le sélecteur. Par exemple :

class Notifier : NSObject  {
    DateTime startTime, endTime;

    [Export ("webViewDidStartLoad:")]
    public void LoadStarted (UIWebView webview)
    {
        startTime = DateTime.Now;
    }

    [Export ("webViewDidFinishLoad:")]
    public void LoadingFinished (UIWebView webView)
    {
        endTime= DateTime.Now;
    }
}

[...]

var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.WeakDelegate = new Notifier ();

Une fois la WeakDelegate propriété affectée, la Delegate propriété n’est pas utilisée. En outre, si vous implémentez la méthode dans une classe de base héritée que vous souhaitez [Exporter], vous devez en faire une méthode publique.

Mappage du Objective-C modèle délégué à C#

Lorsque vous voyez Objective-C des exemples qui ressemblent à ceci :

foo.delegate = [[SomethingDelegate] alloc] init]

Cela indique au langage de créer et de construire un instance de la classe « SomethingDelegate » et d’affecter la valeur à la propriété delegate sur la variable foo. Ce mécanisme est pris en charge par Xamarin.iOS et C#, la syntaxe est la suivante :

foo.Delegate = new SomethingDelegate ();

Dans Xamarin.iOS, nous avons fourni des classes fortement typées qui correspondent aux Objective-C classes déléguées. Pour les utiliser, vous allez sous-classer et remplacer les méthodes définies par l’implémentation de Xamarin.iOS. Pour plus d’informations sur leur fonctionnement, consultez la section « Modèles » ci-dessous.

Mappage des délégués à C#

UIKit utilise Objective-C généralement les délégués sous deux formes.

Le premier formulaire fournit une interface au modèle d’un composant. Par exemple, en tant que mécanisme permettant de fournir des données à la demande pour une vue, comme l’installation de stockage de données pour un affichage Liste. Dans ce cas, vous devez toujours créer une instance de la classe appropriée et affecter la variable.

Dans l’exemple suivant, nous fournissons avec une UIPickerView implémentation pour un modèle qui utilise des chaînes :

public class SampleTitleModel : UIPickerViewTitleModel {

    public override string TitleForRow (UIPickerView picker, nint row, nint component)
    {
        return String.Format ("At {0} {1}", row, component);
    }
}

[...]

pickerView.Model = new MyPickerModel ();

Le deuxième formulaire consiste à fournir une notification pour les événements. Dans ces cas, bien que nous exposions toujours l’API sous la forme décrite ci-dessus, nous fournissons également des événements C#, qui doivent être plus simples à utiliser pour les opérations rapides et intégrés à des délégués anonymes et des expressions lambda en C#.

Par exemple, vous pouvez vous abonner aux UIAccelerometer événements :

UIAccelerometer.SharedAccelerometer.Acceleration += (sender, args) => {
   UIAcceleration acc = args.Acceleration;
   Console.WriteLine ("Time={0} at {1},{2},{3}", acc.Time, acc.X, acc.Y, acc.Z);
}

Les deux options sont disponibles là où elles ont du sens, mais en tant que programmeur, vous devez choisir l’une ou l’autre. Si vous créez votre propre instance d’un répondeur/délégué fortement typé et que vous l’affectez, les événements C# ne seront pas fonctionnels. Si vous utilisez les événements C#, les méthodes de votre classe répondeur/délégué ne seront jamais appelées.

L’exemple précédent utilisé UIWebView peut être écrit à l’aide de lambdaS C# 3.0 comme suit :

var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.LoadStarted += () => { startTime = DateTime.Now; }
web.LoadFinished += () => { endTime = DateTime.Now; }

Réponse aux événements

Dans Objective-C le code, les gestionnaires d’événements pour plusieurs contrôles et les fournisseurs d’informations pour plusieurs contrôles sont parfois hébergés dans la même classe. Cela est possible parce que les classes répondent aux messages, et tant que les classes répondent aux messages, il est possible de lier des objets ensemble.

Comme indiqué précédemment, Xamarin.iOS prend en charge à la fois le modèle de programmation basé sur les événements C# et le Objective-C modèle de délégué, où vous pouvez créer une classe qui implémente le délégué et remplace les méthodes souhaitées.

Il est également possible de prendre en charge Objective-Cle modèle dans lequel les répondeurs pour plusieurs opérations différentes sont tous hébergés dans le même instance d’une classe. Pour ce faire, vous devez toutefois utiliser les fonctionnalités de bas niveau de la liaison Xamarin.iOS.

Par exemple, si vous souhaitez que votre classe réponde à la fois au UITextFieldDelegate.textFieldShouldClearmessage : et au UIWebViewDelegate.webViewDidStartLoadmessage : dans le même instance d’une classe, vous devez utiliser la déclaration d’attribut [Export] :

public class MyCallbacks : NSObject {
    [Export ("textFieldShouldClear:"]
    public bool should_we_clear (UITextField tf)
    {
        return true;
    }

    [Export ("webViewDidStartLoad:")]
    public void OnWebViewStart (UIWebView view)
    {
        Console.WriteLine ("Loading started");
    }
}

Les noms C# des méthodes ne sont pas importants ; tout ce qui compte, ce sont les chaînes passées à l’attribut [Export].

Lorsque vous utilisez ce style de programmation, assurez-vous que les paramètres C# correspondent aux types réels que le moteur d’exécution passera.

Modèles

Dans les installations de stockage UIKit, ou dans les répondeurs implémentés à l’aide de classes d’assistance, ceux-ci sont référencés dans le Objective-C code en tant que délégués, et ils sont implémentés en tant que protocoles.

Objective-C Les protocoles sont comme des interfaces, mais ils prennent en charge les méthodes facultatives, c’est-à-dire que toutes les méthodes n’ont pas besoin d’être implémentées pour que le protocole fonctionne.

Il existe deux façons d’implémenter un modèle. Vous pouvez l’implémenter manuellement ou utiliser les définitions fortement typées existantes.

Le mécanisme manuel est nécessaire lorsque vous essayez d’implémenter une classe qui n’a pas été liée par Xamarin.iOS. C’est facile à faire :

  • Marquer votre classe pour l’inscription auprès du runtime
  • Appliquez l’attribut [Export] avec le nom du sélecteur réel sur chaque méthode que vous souhaitez remplacer
  • Instanciez la classe et passez-la.

Par exemple, les éléments suivants implémentent une seule des méthodes facultatives dans la définition de protocole UIApplicationDelegate :

public class MyAppController : NSObject {
        [Export ("applicationDidFinishLaunching:")]
        public void FinishedLaunching (UIApplication app)
        {
                SetupWindow ();
        }
}

Le Objective-C nom du sélecteur (« applicationDidFinishLaunching : ») est déclaré avec l’attribut Export et la classe est inscrite avec l’attribut [Register] .

Xamarin.iOS fournit des déclarations fortement typées, prêtes à l’emploi, qui ne nécessitent pas de liaison manuelle. Pour prendre en charge ce modèle de programmation, le runtime Xamarin.iOS prend en charge l’attribut [Model] sur une déclaration de classe. Cela informe le runtime qu’il ne doit pas connecter toutes les méthodes de la classe, sauf si les méthodes sont explicitement implémentées.

Cela signifie que dans UIKit, les classes qui représentent un protocole avec des méthodes facultatives sont écrites comme suit :

[Model]
public class SomeViewModel : NSObject {
    [Export ("someMethod:")]
    public virtual int SomeMethod (TheView view) {
       throw new ModelNotImplementedException ();
    }
    ...
}

Lorsque vous souhaitez implémenter un modèle qui implémente uniquement certaines des méthodes, il vous suffit de remplacer les méthodes qui vous intéressent et d’ignorer les autres méthodes. Le runtime raccorde uniquement les méthodes remplacées, et non les méthodes d’origine au Objective-C monde.

L’équivalent de l’exemple manuel précédent est :

public class AppController : UIApplicationDelegate {
    public override void FinishedLaunching (UIApplication uia)
    {
     ...
    }
}

Les avantages sont qu’il n’est pas nécessaire d’explorer les Objective-C fichiers d’en-tête pour trouver le sélecteur, les types des arguments ou le mappage vers C#, et que vous obtenez intellisense à partir de Visual Studio pour Mac, ainsi que des types forts

PRISES XIB et C#

Il s’agit d’une description de bas niveau de la façon dont les outlets s’intègrent à C# et est fournie aux utilisateurs avancés de Xamarin.iOS. Lorsque vous utilisez Visual Studio pour Mac, le mappage est effectué automatiquement en arrière-plan à l’aide du code généré sur le vol pour vous.

Lorsque vous concevez votre interface utilisateur avec Interface Builder, vous concevez uniquement l’apparence de l’application et établissez des connexions par défaut. Si vous souhaitez récupérer des informations par programmation, modifier le comportement d’un contrôle au moment de l’exécution ou modifier le contrôle au moment de l’exécution, il est nécessaire de lier certains contrôles à votre code managé.

Cette opération se fait en quelques étapes :

  1. Ajoutez la déclaration de sortie au propriétaire de votre fichier.
  2. Connectez votre contrôle au propriétaire du fichier.
  3. Stockez l’interface utilisateur et les connexions dans votre fichier XIB/NIB.
  4. Chargez le fichier NIB au moment de l’exécution.
  5. Accédez à la variable outlet.

Les étapes (1) à (3) sont traitées dans la documentation d’Apple pour la création d’interfaces avec Interface Builder.

Lorsque vous utilisez Xamarin.iOS, votre application doit créer une classe qui dérive d’UIViewController. Il est implémenté comme suit :

public class MyViewController : UIViewController {
    public MyViewController (string nibName, NSBundle bundle) : base (nibName, bundle)
    {
        // You can have as many arguments as you want, but you need to call
        // the base constructor with the provided nibName and bundle.
    }
}

Ensuite, pour charger votre ViewController à partir d’un fichier NIB, procédez comme suit :

var controller = new MyViewController ("HelloWorld", NSBundle.MainBundle, this);

Cela charge l’interface utilisateur à partir de la niB. Maintenant, pour accéder aux sorties, il est nécessaire d’informer le runtime que nous voulons y accéder. Pour ce faire, la UIViewController sous-classe doit déclarer les propriétés et les annoter avec l’attribut [Connect]. Comme ceci :

[Connect]
UITextField UserName {
    get {
        return (UITextField) GetNativeField ("UserName");
    }
    set {
        SetNativeField ("UserName", value);
    }
}

L’implémentation de la propriété est celle qui extrait et stocke réellement la valeur du type natif réel.

Vous n’avez pas à vous en soucier lorsque vous utilisez Visual Studio pour Mac et InterfaceBuilder. Visual Studio pour Mac met automatiquement en miroir tous les sorties déclarées avec du code dans une classe partielle compilée dans le cadre de votre projet.

Sélecteurs

Les sélecteurs constituent un concept de base de Objective-C la programmation. Vous rencontrerez souvent des API qui vous obligent à passer un sélecteur ou qui s’attendent à ce que votre code réponde à un sélecteur.

Il est facile de créer des sélecteurs en C# : il vous suffit de créer une nouvelle instance de la ObjCRuntime.Selector classe et d’utiliser le résultat à n’importe quel endroit de l’API qui en a besoin. Par exemple :

var selector_add = new Selector ("add:plus:");

Pour qu’une méthode C# réponde à un appel de sélecteur, elle doit hériter du NSObject type et la méthode C# doit être décorée avec le nom du sélecteur à l’aide de l’attribut [Export] . Par exemple :

public class MyMath : NSObject {
    [Export ("add:plus:")]
    int Add (int first, int second)
    {
         return first + second;
    }
}

Les noms des sélecteurs doivent correspondre exactement, y compris tous les points-virgules intermédiaires et de fin (« : »), le cas échéant.

Constructeurs NSObject

La plupart des classes Xamarin.iOS qui dérivent de NSObject exposent des constructeurs spécifiques aux fonctionnalités de l’objet, mais elles exposent également différents constructeurs qui ne sont pas immédiatement évidents.

Les constructeurs sont utilisés comme suit :

public Foo (IntPtr handle)

Ce constructeur est utilisé pour instancier votre classe lorsque le runtime doit mapper votre classe à une classe non managée. Cela se produit lorsque vous chargez un fichier XIB/NIB. À ce stade, le Objective-C runtime aura créé un objet dans le monde non managé, et ce constructeur sera appelé pour initialiser le côté managé.

En règle générale, il vous suffit d’appeler le constructeur de base avec le paramètre handle et, dans le corps, d’effectuer toute initialisation nécessaire.

public Foo ()

Il s’agit du constructeur par défaut pour une classe, et dans les classes fournies par Xamarin.iOS, cela initialise la classe Foundation.NSObject et toutes les classes comprises entre, et à la fin, la chaîne à la Objective-Cinit méthode sur la classe .

public Foo (NSObjectFlag x)

Ce constructeur est utilisé pour initialiser le instance, mais empêcher le code d’appeler la Objective-C méthode « init » à la fin. Vous l’utilisez généralement lorsque vous vous êtes déjà inscrit pour l’initialisation (lorsque vous utilisez [Export] sur votre constructeur) ou lorsque vous avez déjà effectué votre initialisation par une autre méthode.

public Foo (NSCoder coder)

Ce constructeur est fourni pour les cas où l’objet est initialisé à partir d’un instance NSCoding.

Exceptions

La conception de l’API Xamarin.iOS ne déclenche Objective-C pas d’exceptions en tant qu’exceptions C#. La conception impose qu’aucune mémoire ne soit envoyée au Objective-C monde en premier lieu et que toutes les exceptions qui doivent être produites sont produites par la liaison elle-même avant que des données non valides ne soient transmises au Objective-C monde.

Notifications

Dans iOS et OS X, les développeurs peuvent s’abonner aux notifications diffusées par la plateforme sous-jacente. Pour ce faire, utilisez la NSNotificationCenter.DefaultCenter.AddObserver méthode . La AddObserver méthode accepte deux paramètres : l’un est la notification à laquelle vous souhaitez vous abonner; l’autre est la méthode à appeler lorsque la notification est déclenchée.

Dans Xamarin.iOS et Xamarin.Mac, les clés des différentes notifications sont hébergées sur la classe qui déclenche les notifications. Par exemple, les notifications déclenchées par le UIMenuController sont hébergées en tant que static NSString propriétés dans les UIMenuController classes qui se terminent par le nom « Notification ».

Gestion de la mémoire

Xamarin.iOS dispose d’un récupérateur de mémoire qui s’occupe de libérer des ressources pour vous lorsqu’elles ne sont plus utilisées. En plus du récupérateur de mémoire, tous les objets qui dérivent de NSObject implémentent l’interface System.IDisposable .

NSObject et IDisposable

L’exposition de l’interface IDisposable est un moyen pratique d’aider les développeurs à libérer des objets susceptibles d’encapsuler des blocs de mémoire volumineux (par exemple, un UIImage peut ressembler à un simple pointeur innocent, mais peut pointer vers une image de 2 mégaoctets) et d’autres ressources importantes et finies (comme une mémoire tampon de décodage vidéo).

NSObject implémente l’interface IDisposable et également le modèle .NET Dispose. Cela permet aux développeurs qui sous-classent NSObject de remplacer le comportement Disposer et de libérer leurs propres ressources à la demande. Par exemple, considérez ce contrôleur de vue qui conserve un tas d’images :

class MenuViewController : UIViewController {
    UIImage breakfast, lunch, dinner;
    [...]
    public override void Dispose (bool disposing)
    {
        if (disposing){
             if (breakfast != null) breakfast.Dispose (); breakfast = null;
             if (lunch != null) lunch.Dispose (); lunch = null;
             if (dinner != null) dinner.Dispose (); dinner = null;
        }
        base.Dispose (disposing)
    }
}

Lorsqu’un objet managé est supprimé, il n’est plus utile. Vous avez peut-être toujours une référence aux objets, mais l’objet n’est pas valide pour toutes les intentions et à toutes les fins à ce stade. Pour ce faire, certaines API .NET lèvent une exception ObjectDisposedException si vous essayez d’accéder à des méthodes sur un objet supprimé, par exemple :

var image = UIImage.FromFile ("demo.png");
image.Dispose ();
image.XXX = false;  // this at this point is an invalid operation

Même si vous pouvez toujours accéder à la variable « image », il s’agit en fait d’une référence non valide et ne pointe plus vers l’objet Objective-C qui contenait l’image.

Toutefois, la suppression d’un objet en C# ne signifie pas que l’objet sera nécessairement détruit. Tout ce que vous faites est de libérer la référence que C# avait sur l’objet . Il est possible que l’environnement Cocoa ait gardé une référence autour pour son propre usage. Par exemple, si vous définissez la propriété Image d’un UIImageView sur une image, puis que vous supprimez l’image, l’UIImageView sous-jacent a pris sa propre référence et conserve une référence à cet objet jusqu’à ce qu’il ait fini de l’utiliser.

Quand appeler Dispose

Appelez Dispose lorsque vous avez besoin de Mono pour vous débarrasser de votre objet. Un cas d’usage possible est lorsque Mono ne sait pas que votre objet NSObject contient une référence à une ressource importante comme la mémoire ou un pool d’informations. Dans ce cas, vous devez appeler Dispose pour libérer immédiatement la référence à la mémoire, au lieu d’attendre que Mono effectue un cycle de garbage collection.

En interne, lorsque Mono crée des références NSString à partir de chaînes C#, il les supprime immédiatement pour réduire la quantité de travail que le récupérateur de mémoire doit effectuer. Moins il y a d’objets à traiter, plus le gc s’exécute rapidement.

Quand conserver les références aux objets

L’un des effets secondaires de la gestion automatique de la mémoire est que le gc se débarrasse des objets inutilisés tant qu’il n’y a aucune référence à ceux-ci. Ce qui peut parfois avoir des effets secondaires surprenants, par exemple, si vous créez une variable locale pour contenir votre contrôleur d’affichage de niveau supérieur, ou votre fenêtre de niveau supérieur, puis que ces éléments disparaissent derrière votre dos.

Si vous ne conservez pas de référence dans vos variables statiques ou instance à vos objets, Mono appellera avec plaisir la méthode Dispose() sur ceux-ci et libérera la référence à l’objet. Étant donné qu’il peut s’agir de la seule référence en attente, le Objective-C runtime détruit l’objet pour vous.