Arquitetura de aplicativos iOS

Os aplicativos Xamarin.iOS são executados no ambiente de execução Mono e usam a compilação completa Ahead of Time (AOT) para compilar código C# para a linguagem assembly ARM. Isso é executado lado a lado com o Objective-C Runtime. Ambos os ambientes de tempo de execução são executados sobre um kernel semelhante ao UNIX, especificamente XNU, e expõem várias APIs ao código do usuário, permitindo que os desenvolvedores acessem o sistema nativo ou gerenciado subjacente.

O diagrama abaixo mostra uma visão geral básica dessa arquitetura:

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

Código nativo e gerenciado: uma explicação

Ao desenvolver para o Xamarin, os termos código nativo e gerenciado são frequentemente usados. Código gerenciado é o código que tem sua execução gerenciada pelo .NET Framework Common Language Runtime ou, no caso do Xamarin: o Mono Runtime. É o que chamamos de linguagem intermediária.

Código nativo é o código que será executado nativamente na plataforma específica (por exemplo, Objective-C ou mesmo código compilado AOT, em um chip ARM). Este guia explora como o AOT compila seu código gerenciado para código nativo e explica como um aplicativo Xamarin.iOS funciona, fazendo pleno uso das APIs iOS da Apple por meio do uso de associações, enquanto também tem acesso ao . BCL da NET e uma linguagem sofisticada como C#.

AOT

Quando você compila qualquer aplicativo da plataforma Xamarin, o compilador Mono C# (ou F#) será executado e compilará seu código C# e F# no Microsoft Intermediate Language (MSIL). Se você estiver executando um Xamarin.Android, um aplicativo Xamarin.Mac ou até mesmo um aplicativo Xamarin.iOS no simulador, o .NET Common Language Runtime (CLR) compila o MSIL usando um compilador Just in Time (JIT). Em tempo de execução, isso é compilado em um código nativo, que pode ser executado na arquitetura correta para seu aplicativo.

No entanto, há uma restrição de segurança no iOS, definida pela Apple, que não permite a execução de código gerado dinamicamente em um dispositivo. Para garantir que aderimos a esses protocolos de segurança, o Xamarin.iOS usa um compilador Ahead of Time (AOT) para compilar o código gerenciado. Isso produz um binário iOS nativo, opcionalmente otimizado com LLVM para dispositivos, que pode ser implantado no processador baseado em ARM da Apple. Um diagrama aproximado de como isso se encaixa é ilustrado abaixo:

A rough diagram of how this fits together

O uso do AOT tem uma série de limitações, que são detalhadas no guia Limitações . Ele também fornece uma série de melhorias em relação ao JIT através de uma redução no tempo de inicialização e várias otimizações de desempenho

Agora que exploramos como o código é compilado do código-fonte para o código nativo, vamos dar uma olhada sob o capô para ver como o Xamarin.iOS nos permite escrever aplicativos iOS totalmente nativos

Seletores

Com o Xamarin, temos dois ecossistemas separados, .NET e Apple, que precisamos reunir para parecer o mais simplificado possível, para garantir que o objetivo final seja uma experiência de usuário suave. Vimos na seção acima como os dois tempos de execução se comunicam, e você pode muito bem ter ouvido falar do termo 'bindings' que permite que as APIs nativas do iOS sejam usadas no Xamarin. As ligações são explicadas em profundidade em nossa Objective-C documentação vinculativa , então por enquanto vamos explorar como o iOS funciona sob o capô.

Primeiro, tem que haver uma maneira de expor Objective-C ao C#, o que é feito via seletores. Um seletor é uma mensagem que é enviada para um objeto ou classe. Com Objective-C isso é feito através das funções objc_msgSend. Para obter mais informações sobre como usar seletores, consulte o Objective-C guia Seletores . Também tem que haver uma maneira de expor o código gerenciado ao Objective-C, o que é mais complicado devido ao fato de que Objective-C não sabe nada sobre o código gerenciado. Para contornar isso, usamos Registrarso . Estes são explicados em mais detalhes na próxima seção.

Registrars

Como mencionado acima, o código é que expõe o registrar código gerenciado ao Objective-C. Ele faz isso criando uma lista de cada classe gerenciada que deriva de NSObject:

  • Para todas as classes que não estão encapsulando uma classe existente Objective-C , ela cria uma nova Objective-C classe com Objective-C membros espelhando todos os membros gerenciados que têm um atributo [Export].

  • Nas implementações para cada membro Objective–C, o código é adicionado automaticamente para chamar o membro gerenciado espelhado.

O pseudocódigo abaixo mostra um exemplo de como isso é feito:

C# (Código gerenciado)

 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

O código gerenciado pode conter os atributos [Register] e [Export], que o registrar usa para saber que o objeto precisa ser exposto a Objective-C. O [Register] atributo é usado para especificar o nome da classe gerada Objective-C caso o nome gerado padrão não seja adequado. Todas as classes derivadas do NSObject são registradas automaticamente no Objective-C. O atributo required [Export] contém uma cadeia de caracteres, que é o seletor usado na classe gerada Objective-C .

Existem dois tipos de registrars usados no Xamarin.iOS – dinâmico e estático:

  • Dinâmico registrars – A dinâmica registrar faz o registro de todos os tipos em seu assembly em tempo de execução. Ele faz isso usando funções fornecidas pela Objective-CAPI de tempo de execução do . A dinâmica registrar , portanto, tem uma inicialização mais lenta, mas um tempo de construção mais rápido. Isso é padrão para o simulador do iOS. Funções nativas (geralmente em C), chamadas de trampolins, são usadas como implementações de método ao usar o dinâmico registrars. Eles variam entre diferentes arquiteturas.

  • Estático registrars – O estático registrar gera Objective-C código durante a compilação, que é então compilado em uma biblioteca estática e vinculado ao executável. Isso permite uma inicialização mais rápida, mas leva mais tempo durante o tempo de compilação. Isso é usado por padrão para compilações de dispositivo. O estático registrar também pode ser usado com o simulador do iOS, passando --registrar:static como um mtouch atributo nas opções de compilação do seu projeto, como mostrado abaixo:

    Setting Additional mtouch arguments

Para obter mais informações sobre as especificidades do sistema de Registro de Tipo do iOS usado pelo Xamarin.iOS, consulte o Guia de Tipo Registrar .

Inicialização do aplicativo

O ponto de entrada de todos os executáveis Xamarin.iOS é fornecido por uma função chamada xamarin_main, que inicializa mono.

Dependendo do tipo de projeto, o seguinte é feito:

  • Para aplicativos iOS e tvOS comuns, o método Main gerenciado, fornecido pelo aplicativo Xamarin, é chamado. Esse método Main gerenciado chama UIApplication.Main, que é o ponto de entrada para Objective-C. UIApplication.Main é a ligação para Objective-Co método 's UIApplicationMain .
  • Para extensões, a função nativa – NSExtensionMain ou (NSExtensionmain para extensões do WatchOS) – fornecida pelas bibliotecas da Apple é chamada. Como esses projetos são bibliotecas de classes e não projetos executáveis, não há métodos Main gerenciados para executar.

Toda essa sequência de inicialização é compilada em uma biblioteca estática, que é então vinculada ao executável final para que seu aplicativo saiba como sair do papel.

Neste ponto, nosso aplicativo foi iniciado, o Mono está em execução, estamos em código gerenciado e sabemos como chamar o código nativo e ser chamado de volta. A próxima coisa que precisamos fazer é realmente começar a adicionar controles e tornar o aplicativo interativo.

Gerador

O Xamarin.iOS contém definições para cada API do iOS. Você pode navegar por qualquer um deles no repositório github do MaciOS. Essas definições contêm interfaces com atributos, bem como quaisquer métodos e propriedades necessários. Por exemplo, o código a seguir é usado para definir um UIToolbar no namespace UIKit. Observe que é uma interface com vários métodos e propriedades:

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

    ...
}

O gerador, chamado btouch em Xamarin.iOS, pega esses arquivos de definição e usa ferramentas .NET para compilá-los em um assembly temporário. No entanto, esse assembly temporário não é utilizável para chamar Objective-C código. O gerador, em seguida, lê o assembly temporário e gera código C# que pode ser usado em tempo de execução. É por isso que, por exemplo, se você adicionar um atributo aleatório ao seu arquivo de .cs definição, ele não aparecerá no código de saída. O gerador não sabe sobre ele e, portanto, btouch não sabe procurá-lo na montagem temporária para produzi-lo.

Uma vez que o Xamarin.iOS.dll tenha sido criado, o mtouch agrupará todos os componentes juntos.

Em alto nível, ele consegue isso executando as seguintes tarefas:

  • Crie uma estrutura de pacote de aplicativos.
  • Copie em seus assemblies gerenciados.
  • Se a vinculação estiver habilitada, execute o vinculador gerenciado para otimizar suas montagens copiando partes não utilizadas.
  • Compilação AOT.
  • Crie um executável nativo, que produz uma série de bibliotecas estáticas (uma para cada assembly) que são vinculadas ao executável nativo, de modo que o executável nativo consiste no código do iniciador, no registrar código (se estático) e em todas as saídas do compilador AOT

Para obter informações mais detalhadas sobre o vinculador e como ele é usado, consulte o Guia do vinculador .

Resumo

Este guia analisou a compilação AOT de aplicativos Xamarin.iOS e explorou o Xamarin.iOS e sua relação com o Objective-C Xamarin.iOS em profundidade.