Architettura delle app iOS

Le applicazioni Xamarin.iOS vengono eseguite all'interno dell'ambiente di esecuzione Mono e usano la compilazione full Ahead of Time (AOT) per compilare il codice C# nel linguaggio assembly ARM. Questa operazione viene eseguita side-by-side con il Objective-C runtime. Entrambi gli ambienti di runtime vengono eseguiti su un kernel simile a UNIX, in particolare XNU, ed espongono varie API al codice utente che consente agli sviluppatori di accedere al sistema nativo o gestito sottostante.

Il diagramma seguente illustra una panoramica di base di questa architettura:

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

Codice nativo e gestito: spiegazione

Quando si sviluppa per Xamarin, vengono spesso usati i termini codice nativo e gestito . Il codice gestito è codice che ha l'esecuzione gestita da Common Language Runtime di .NET Framework o nel caso di Xamarin: Mono Runtime. Questo è ciò che chiamiamo un linguaggio intermedio.

Il codice nativo è codice che verrà eseguito in modo nativo nella piattaforma specifica (ad esempio, Objective-C o anche codice compilato AOT, in un chip ARM). Questa guida illustra il modo in cui AOT compila il codice gestito nel codice nativo e spiega il funzionamento di un'applicazione Xamarin.iOS, che usa tutte le API iOS di Apple tramite l'uso di associazioni, pur avendo accesso a . BCL di NET e un linguaggio sofisticato, ad esempio C#.

AOT

Quando si compila un'applicazione di piattaforma Xamarin, il compilatore Mono C# (o F#) verrà eseguito e compilerà il codice C# e F# in Microsoft Intermediate Language (MSIL). Se si esegue un'applicazione Xamarin.Android, un'applicazione Xamarin.Mac o anche un'applicazione Xamarin.iOS nel simulatore, . NET Common Language Runtime (CLR) compila MSIL usando un compilatore JIT (Just in Time). In fase di esecuzione questa operazione viene compilata in un codice nativo, che può essere eseguito sull'architettura corretta per l'applicazione.

Tuttavia, esiste una restrizione di sicurezza per iOS, impostata da Apple, che impedisce l'esecuzione di codice generato dinamicamente in un dispositivo. Per assicurarsi di rispettare questi protocolli di sicurezza, Xamarin.iOS usa invece un compilatore AOT (Ahead of Time) per compilare il codice gestito. Questo produce un file binario iOS nativo, facoltativamente ottimizzato con LLVM per i dispositivi, che può essere distribuito nel processore basato su ARM di Apple. Di seguito è illustrato un diagramma approssimativo del modo in cui questo comportamento si integra:

A rough diagram of how this fits together

L'uso di AOT presenta una serie di limitazioni, descritte in dettaglio nella Guida alle limitazioni . Offre inoltre numerosi miglioramenti rispetto a JIT tramite una riduzione del tempo di avvio e varie ottimizzazioni delle prestazioni

Ora che è stato esaminato il modo in cui il codice viene compilato dall'origine al codice nativo, si esaminerà il modo in cui Xamarin.iOS consente di scrivere applicazioni iOS completamente native

Selettori

Con Xamarin, sono disponibili due ecosistemi separati, .NET e Apple, che è necessario riunire per sembrare il più semplice possibile, per garantire che l'obiettivo finale sia un'esperienza utente fluida. Nella sezione precedente è stato illustrato il modo in cui i due runtime comunicano e si potrebbe aver sentito molto bene il termine "binding" che consente l'uso delle API iOS native in Xamarin. Le associazioni sono descritte in modo approfondito nella Objective-C documentazione di binding , quindi per ora si esaminerà il funzionamento di iOS sotto le quinte.

Prima di tutto, è necessario esporre Objective-C a C#, operazione eseguita tramite selettori. Un selettore è un messaggio che viene inviato a un oggetto o a una classe. Questa Objective-C operazione viene eseguita tramite le funzioni di objc_msgSend . Per altre informazioni sull'uso dei selettori, vedere la Objective-C guida ai selettori . Esiste anche un modo per esporre il codice gestito a Objective-C, che è più complicato a causa del fatto che Objective-C non conosce nulla del codice gestito. Per aggirare questo problema, si usa Registrars. Queste informazioni sono descritte in modo più dettagliato nella sezione successiva.

Registrars

Come accennato in precedenza, è il registrar codice che espone il codice gestito a Objective-C. A tale scopo, creare un elenco di ogni classe gestita che deriva da NSObject:

  • Per tutte le classi che non eseguono il wrapping di una classe esistente Objective-C , crea una nuova Objective-C classe con Objective-C membri che eseguono il mirroring di tutti i membri gestiti con un attributo [Export].

  • Nelle implementazioni per ogni membro Objective-C, il codice viene aggiunto automaticamente per chiamare il membro gestito con mirroring.

Lo pseudo-codice seguente mostra un esempio di come viene eseguita questa operazione:

C# (codice gestito)

 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

Il codice gestito può contenere gli attributi [Register] e [Export], usati registrar da per sapere che l'oggetto deve essere esposto a Objective-C. L'attributo [Register] viene usato per specificare il nome della classe generata Objective-C nel caso in cui il nome generato predefinito non sia adatto. Tutte le classi derivate da NSObject vengono registrate automaticamente con Objective-C. L'attributo obbligatorio [Export] contiene una stringa, ovvero il selettore usato nella classe generata Objective-C .

Esistono due tipi di registrars usati in Xamarin.iOS: dinamici e statici:

  • Dinamico registrars : la dinamica registrar esegue la registrazione di tutti i tipi nell'assembly in fase di esecuzione. A tale scopo, usare le funzioni fornite dall'API Objective-Cdi runtime. Il dinamico registrar ha quindi un avvio più lento, ma un tempo di compilazione più veloce. Si tratta dell'impostazione predefinita per il simulatore iOS. Le funzioni native (in genere in C), chiamate trampolini, vengono usate come implementazioni del metodo quando si usa il dinamico registrars. Variano tra architetture diverse.

  • Statico: il codice statico registrars genera Objective-C codice durante la compilazione, che viene quindi compilato in una libreria statica registrar e collegato all'eseguibile. Ciò consente un avvio più rapido, ma richiede più tempo durante il tempo di compilazione. Questa opzione viene usata per impostazione predefinita per le compilazioni dei dispositivi. Il simulatore statico registrar può essere usato anche con il simulatore iOS passando --registrar:static come mtouch attributo nelle opzioni di compilazione del progetto, come illustrato di seguito:

    Setting Additional mtouch arguments

Per altre informazioni sulle specifiche del sistema di registrazione dei tipi iOS usato da Xamarin.iOS, vedere la Guida al tipo Registrar .

Avvio dell'applicazione

Il punto di ingresso di tutti gli eseguibili Xamarin.iOS viene fornito da una funzione denominata xamarin_main, che inizializza mono.

A seconda del tipo di progetto, viene eseguita la procedura seguente:

  • Per le normali applicazioni iOS e tvOS, viene chiamato il metodo Main gestito, fornito dall'app Xamarin. Questo metodo Main gestito chiama UIApplication.Mainquindi , che è il punto di ingresso per Objective-C. UIApplication.Main è l'associazione per Objective-Cil metodo di UIApplicationMain .
  • Per le estensioni, viene chiamata la funzione nativa o NSExtensionMain (NSExtensionmain per le estensioni WatchOS) fornita dalle librerie Apple. Poiché questi progetti sono librerie di classi e non progetti eseguibili, non esistono metodi Main gestiti da eseguire.

Tutta questa sequenza di avvio viene compilata in una libreria statica, che viene quindi collegata al file eseguibile finale, in modo che l'app sappia come uscire da zero.

A questo punto l'app è stata avviata, Mono è in esecuzione, è in codice gestito e si sa come chiamare il codice nativo ed essere richiamato. L'operazione successiva da eseguire consiste nell'iniziare effettivamente ad aggiungere controlli e rendere interattiva l'app.

Generatore

Xamarin.iOS contiene definizioni per ogni singola API iOS. È possibile esplorare uno di questi nel repository GitHub MaciOS. Queste definizioni contengono interfacce con attributi, nonché i metodi e le proprietà necessari. Ad esempio, il codice seguente viene usato per definire un uiToolbar nello spazio dei nomi UIKit. Si noti che si tratta di un'interfaccia con diversi metodi e proprietà:

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

    ...
}

Il generatore, chiamato btouch in Xamarin.iOS, accetta questi file di definizione e usa gli strumenti .NET per compilarli in un assembly temporaneo. Tuttavia, questo assembly temporaneo non è utilizzabile per chiamare Objective-C il codice. Il generatore legge quindi l'assembly temporaneo e genera codice C# che può essere usato in fase di esecuzione. Questo è il motivo per cui, ad esempio, se si aggiunge un attributo casuale al file di definizione .cs, non verrà visualizzato nel codice restituito. Il generatore non lo conosce e quindi btouch non sa cercarlo nell'assembly temporaneo per restituirlo.

Dopo aver creato il Xamarin.iOS.dll, mtouch aggrega tutti i componenti.

A livello generale, questo risultato viene ottenuto eseguendo le attività seguenti:

  • Creare una struttura di bundle dell'app.
  • Copiare negli assembly gestiti.
  • Se il collegamento è abilitato, eseguire il linker gestito per ottimizzare gli assembly eliminando le parti inutilizzate.
  • Compilazione AOT.
  • Creare un eseguibile nativo, che restituisce una serie di librerie statiche (una per ogni assembly) collegate all'eseguibile nativo, in modo che l'eseguibile nativo sia costituito dal codice dell'utilità di avvio, dal registrar codice (se statico) e da tutti gli output del compilatore AOT

Per informazioni più dettagliate sul linker e su come viene usato, vedere la guida del linker .

Riepilogo

Questa guida ha esaminato la compilazione AOT delle app Xamarin.iOS ed è stata esaminata in modo approfondito Xamarin.iOS e la relativa relazione Objective-C .