Architecture d’application iOS

Les applications Xamarin.iOS s’exécutent dans l’environnement d’exécution Mono et utilisent une compilation complète à l’avance (AOT) pour compiler du code C# dans le langage d’assembly ARM. Cela s’exécute côte à côte avec le Objective-C runtime. Les deux environnements d’exécution s’exécutent sur un noyau de type UNIX, en particulier XNU, et exposent diverses API au code utilisateur, ce qui permet aux développeurs d’accéder au système natif ou managé sous-jacent.

Le diagramme ci-dessous présente une vue d’ensemble de base de cette architecture :

This diagram shows a basic overview of the Ahead of Time (AOT) compilation architecture

Code natif et managé : Explication

Lors du développement pour Xamarin, les termes natifs et le code managé sont souvent utilisés. Le code managé est du code qui a son exécution gérée par le Common Language Runtime .NET Framework, ou dans le cas de Xamarin : le Runtime Mono. C’est ce que nous appelons un langage intermédiaire.

Le code natif est du code qui s’exécute en mode natif sur la plateforme spécifique (par exemple, Objective-C ou même du code compilé AOT, sur une puce ARM). Ce guide explique comment AOT compile votre code managé en code natif et explique comment une application Xamarin.iOS fonctionne, en utilisant pleinement les API iOS d’Apple via l’utilisation de liaisons, tout en ayant accès à . BCL de NET et langage sophistiqué comme C#.

AOT

Lorsque vous compilez une application de plateforme Xamarin, le compilateur Mono C# (ou F#) s’exécute et compile votre code C# et F# dans MICROSOFT Intermediate Language (MSIL). Si vous exécutez une application Xamarin.Android, Xamarin.Mac ou même une application Xamarin.iOS sur le simulateur, le Common Language Runtime (CLR) .NET compile le MSIL à l’aide d’un compilateur juste-à-temps (JIT). Au moment de l’exécution, il est compilé dans un code natif, qui peut s’exécuter sur l’architecture appropriée pour votre application.

Toutefois, il existe une restriction de sécurité sur iOS, définie par Apple, qui interdit l’exécution de code généré dynamiquement sur un appareil. Pour nous assurer que nous respectons ces protocoles de sécurité, Xamarin.iOS utilise plutôt un compilateur À l’avance (AOT) pour compiler le code managé. Cela produit un binaire iOS natif, éventuellement optimisé avec LLVM pour les appareils, qui peut être déployé sur le processeur ARM d’Apple. Un diagramme approximatif de la façon dont cela s’intègre est illustré ci-dessous :

A rough diagram of how this fits together

L’utilisation d’AOT présente un certain nombre de limitations, qui sont détaillées dans le guide des limitations . Il fournit également un certain nombre d’améliorations par rapport à JIT grâce à une réduction du temps de démarrage et à diverses optimisations des performances

Maintenant que nous avons exploré la façon dont le code est compilé de la source au code natif, examinons la façon dont Xamarin.iOS nous permet d’écrire des applications iOS entièrement natives

Sélecteurs

Avec Xamarin, nous avons deux écosystèmes distincts, .NET et Apple, que nous devons rassembler pour sembler aussi rationalisé que possible, pour garantir que l’objectif final est une expérience utilisateur fluide. Nous avons vu dans la section ci-dessus comment les deux runtimes communiquent, et vous avez peut-être très bien entendu entendu parler du terme « bindings » qui permet aux API iOS natives d’être utilisées dans Xamarin. Les liaisons sont expliquées en détail dans notre Objective-C documentation de liaison . Pour l’instant, nous allons explorer le fonctionnement d’iOS sous le capot.

Tout d’abord, il doit y avoir un moyen d’exposer Objective-C à C#, ce qui est fait via des sélecteurs. Un sélecteur est un message envoyé à un objet ou à une classe. Pour Objective-C ce faire, utilisez les fonctions objc_msgSend . Pour plus d’informations sur l’utilisation des sélecteurs, reportez-vous au Objective-C guide des sélecteurs . Il doit également y avoir un moyen d’exposer du code managé à Objective-C, ce qui est plus compliqué en raison du fait que Objective-C rien ne connaît le code managé. Pour contourner ce problème, nous utilisons Registrars. Celles-ci sont expliquées plus en détail dans la section suivante.

Registrars

Comme mentionné ci-dessus, il s’agit du registrar code qui expose le code managé à Objective-C. Pour ce faire, créez une liste de chaque classe managée qui dérive de NSObject :

  • Pour toutes les classes qui n’encapsulent pas une classe existanteObjective-C, elle crée une Objective-C classe avec Objective-C des membres miroir ing tous les membres gérés qui ont un attribut [Export].

  • Dans les implémentations de chaque membre Objective-C, le code est ajouté automatiquement pour appeler le membre managé miroir ed.

Le pseudo-code ci-dessous montre un exemple de la façon dont cela est effectué :

C# (Code managé)

 class MyViewController : UIViewController{
     [Export ("myFunc")]
     public void MyFunc ()
     {
     }
 }

Objective-C:

@interface MyViewController : UIViewController { }

    -(void)myFunc;
@end

@implementation MyViewController {}

    -(void) myFunc
    {
        /* code to call the managed MyViewController.MyFunc method */
    }
@end

Le code managé peut contenir les attributs et [Register][Export], que les registrar utilisations permettent de savoir que l’objet doit être exposé à Objective-C. L’attribut [Register] est utilisé pour spécifier le nom de la classe générée Objective-C si le nom généré par défaut n’est pas approprié. Toutes les classes dérivées de NSObject sont automatiquement inscrites auprès Objective-Cde . L’attribut requis [Export] contient une chaîne, qui est le sélecteur utilisé dans la classe générée Objective-C .

Il existe deux types d’utilisation registrars dans Xamarin.iOS : dynamique et statique :

  • Dynamique registrars : la dynamique registrar effectue l’inscription de tous les types dans votre assembly au moment de l’exécution. Pour ce faire, utilisez des fonctions fournies par Objective-Cl’API runtime. La dynamique registrar a donc un démarrage plus lent, mais un temps de génération plus rapide. Il s’agit de la valeur par défaut du simulateur iOS. Les fonctions natives (généralement en C), appelées trampolines, sont utilisées comme implémentations de méthode lors de l’utilisation de la dynamique registrars. Ils varient d’une architecture à l’autre.

  • Statique registrars : la statique registrar génère Objective-C du code pendant la build, qui est ensuite compilée dans une bibliothèque statique et liée à l’exécutable. Cela permet un démarrage plus rapide, mais prend plus de temps pendant la génération. Cela est utilisé par défaut pour les builds d’appareils. La statique registrar peut également être utilisée avec le simulateur iOS en passant --registrar:static en tant qu’attribut mtouch dans les options de génération de votre projet, comme indiqué ci-dessous :

    Setting Additional mtouch arguments

Pour plus d’informations sur les spécificités du système d’inscription de type iOS utilisé par Xamarin.iOS, reportez-vous au guide de type Registrar .

Lancement de l’application

Le point d’entrée de tous les exécutables Xamarin.iOS est fourni par une fonction appelée xamarin_main, qui initialise mono.

Selon le type de projet, les opérations suivantes sont effectuées :

  • Pour les applications iOS et tvOS standard, la méthode Main managée fournie par l’application Xamarin est appelée. Cette méthode main managée appelle UIApplication.Mainensuite , qui est le point d’entrée pour Objective-C. UIApplication.Main est la liaison pour Objective-Cla méthode ' .UIApplicationMain
  • Pour les extensions, la fonction NSExtensionMain native (ou (NSExtensionmain pour les extensions WatchOS) fournie par les bibliothèques Apple est appelée. Étant donné que ces projets sont des bibliothèques de classes et non des projets exécutables, il n’existe aucune méthode main managée à exécuter.

Toutes ces séquences de lancement sont compilées dans une bibliothèque statique, qui est ensuite liée à votre exécutable final afin que votre application sache comment sortir du terrain.

À ce stade, notre application a démarré, Mono est en cours d’exécution, nous sommes dans du code managé et nous savons comment appeler du code natif et être rappelés. La prochaine chose à faire consiste à commencer à ajouter des contrôles et à rendre l’application interactive.

Générateur

Xamarin.iOS contient des définitions pour chaque API iOS unique. Vous pouvez parcourir l’un de ces éléments sur le dépôt github MaciOS. Ces définitions contiennent des interfaces avec des attributs, ainsi que toutes les méthodes et propriétés nécessaires. Par exemple, le code suivant est utilisé pour définir une barre UIToolbar dans l’espace de noms UIKit. Notez qu’il s’agit d’une interface avec plusieurs méthodes et propriétés :

[BaseType (typeof (UIView))]
public interface UIToolbar : UIBarPositioning {
    [Export ("initWithFrame:")]
    IntPtr Constructor (CGRect frame);

    [Export ("barStyle")]
    UIBarStyle BarStyle { get; set; }

    [Export ("items", ArgumentSemantic.Copy)][NullAllowed]
    UIBarButtonItem [] Items { get; set; }

    [Export ("translucent", ArgumentSemantic.Assign)]
    bool Translucent { [Bind ("isTranslucent")] get; set; }

    // done manually so we can keep this "in sync" with 'Items' property
    //[Export ("setItems:animated:")][PostGet ("Items")]
    //void SetItems (UIBarButtonItem [] items, bool animated);

    [Since (5,0)]
    [Export ("setBackgroundImage:forToolbarPosition:barMetrics:")]
    [Appearance]
    void SetBackgroundImage ([NullAllowed] UIImage backgroundImage, UIToolbarPosition position, UIBarMetrics barMetrics);

    [Since (5,0)]
    [Export ("backgroundImageForToolbarPosition:barMetrics:")]
    [Appearance]
    UIImage GetBackgroundImage (UIToolbarPosition position, UIBarMetrics barMetrics);

    ...
}

Le générateur, appelé btouch dans Xamarin.iOS, prend ces fichiers de définition et utilise les outils .NET pour les compiler dans un assembly temporaire. Toutefois, cet assembly temporaire n’est pas utilisable pour appeler Objective-C du code. Le générateur lit ensuite l’assembly temporaire et génère du code C# qui peut être utilisé au moment de l’exécution. C’est pourquoi, par exemple, si vous ajoutez un attribut aléatoire à votre fichier de définition .cs, il ne s’affiche pas dans le code généré. Le générateur ne le connaît pas et ne sait donc btouch pas le rechercher dans l’assembly temporaire pour le générer.

Une fois le Xamarin.iOS.dll créé, mtouch regroupe tous les composants.

À un niveau élevé, elle effectue cette opération en exécutant les tâches suivantes :

  • Créez une structure d’offre groupée d’applications.
  • Copiez dans vos assemblys managés.
  • Si la liaison est activée, exécutez l’éditeur de liens managé pour optimiser vos assemblys en déchirant les parties inutilisées.
  • Compilation AOT.
  • Créez un exécutable natif, qui génère une série de bibliothèques statiques (une pour chaque assembly) liées à l’exécutable natif, afin que l’exécutable natif se compose du code du lanceur, du registrar code (si statique) et de toutes les sorties du compilateur AOT

Pour plus d’informations sur l’éditeur de liens et la façon dont il est utilisé, reportez-vous au guide de l’éditeur de liens.

Résumé

Ce guide a examiné la compilation AOT des applications Xamarin.iOS, et a exploré Xamarin.iOS et sa relation avec Objective-C profondeur.