Funcionamiento de Xamarin.Mac

La mayoría de las veces el desarrollador nunca tendrá que preocuparse por la "magia" interna de Xamarin.Mac, sin embargo, tener una comprensión aproximada de cómo funcionan las cosas en el interior ayudará tanto a interpretar la documentación existente con una lente de C# como con problemas de depuración, cuando surjan.

En Xamarin.Mac, una aplicación puentea dos mundos: está el Objective-C basado en runtime que contiene instancias de clases nativas (NSString, NSApplication, etc.) y el runtime de C# que contiene instancias de clases administradas (System.String, HttpClient, etc.). Entre estos dos mundos, Xamarin.Mac crea un puente bidireccional para que una aplicación pueda llamar a métodos (selectores) en Objective-C (como NSApplication.Init) y Objective-C puede volver a llamar a los métodos de C# de la aplicación (como los métodos en un delegado de aplicación). En general, las llamadas a Objective-C se controlan de forma transparente a través de P/Invokes y algún código de runtime que proporciona Xamarin.

Exponer clases o métodos de C# a Objective-C

Sin embargo, para que Objective-C vuelva a llamar a los objetos de C# de una aplicación, deben exponerse de una manera que Objective-C pueda entender. Esto se hace a través de los atributos Register y Export. Considere el ejemplo siguiente:

[Register ("MyClass")]
public class MyClass : NSObject
{
   [Export ("init")]
   public MyClass ()
   {
   }

   [Export ("run")]
   public void Run ()
   {
   }
}

En este ejemplo, el runtime Objective-C, ahora, conocerá una clase denominada MyClass con selectores denominados init y run.

En la mayoría de los casos, se trata de un detalle de implementación que el desarrollador puede omitir, ya que la mayoría de las llamadas de retorno que recibe una aplicación serán a través de métodos invalidados en clases base (como AppDelegate, Delegates, DataSources) o en Acciones pasadas a las API. En todos esos casos, los atributos Export no son necesarios en el código de C#.

Ejecución a través del constructor

En muchos casos, el desarrollador tendrá que exponer la API de construcción de clases de C# de la aplicación al runtime Objective-C para que se pueda crear una instancia desde lugares como cuando se llama en archivos Storyboard o XIB. Estos son los cinco constructores más comunes que se usan en las aplicaciones de Xamarin.Mac:

// Called when created from unmanaged code
public CustomView (IntPtr handle) : base (handle)
{
   Initialize ();
}

// Called when created directly from a XIB file
[Export ("initWithCoder:")]
public CustomView (NSCoder coder) : base (coder)
{
   Initialize ();
}

// Called from C# to instance NSView with a Frame (initWithFrame)
public CustomView (CGRect frame) : base (frame)
{
}

// Called from C# to instance NSView without setting the frame (init)
public CustomView () : base ()
{
}

// This is a special case constructor that you call on a derived class when the derived called has an [Export] constructor.
// For example, if you call init on NSString then you don’t want to call init on NSObject.
public CustomView () : base (NSObjectFlag.Empty)
{
}

En general, el desarrollador debe dejar solos a los constructores IntPtr y NSCoder que se generan al crear algunos tipos como personalizados NSViews. Si Xamarin.Mac necesita llamar a uno de estos constructores en respuesta a una solicitud en runtime Objective-C y lo ha quitado, la aplicación se bloqueará dentro del código nativo y puede ser difícil averiguar, exactamente, el problema.

Administración y ciclos de memoria

La administración de memoria en Xamarin.Mac es muy similar a Xamarin.iOS. También es un tema complejo, uno más allá del ámbito de este documento. Lea los Procedimientos recomendados de memoria y rendimiento.

Compilación anticipada

Normalmente, las aplicaciones .NET no se compilan en código de máquina cuando se construyen, sino que se compilan en una capa intermedia denominada código IL que obtiene el Just-In-Time (JIT) compilado en código de máquina cuando se inicia la aplicación.

El tiempo que tarda el entorno de ejecución Mono en compilar JIT este código de máquina puede ralentizar el inicio de una aplicación de Xamarin.Mac hasta un 20 %, ya que tarda en generarse el código de máquina necesario.

Debido a las limitaciones impuestas por Apple en iOS, la compilación JIT del código IL no está disponible para Xamarin.iOS. Como resultado, todas las aplicaciones de Xamarin.iOS son compiladas Ahead-Of-Time (AOT) completas en código de máquina durante el ciclo de compilación.

La novedad de Xamarin.Mac es la capacidad de AOT del código IL durante el ciclo de compilación de la aplicación, como puede hacerlo Xamarin.iOS. Xamarin.Mac usa un enfoque AOT Híbrido que compila la mayoría del código de máquina necesario, pero permite que el runtime compile trampolines necesarios y la flexibilidad para seguir admitiendo Reflection.Emit (y otros casos de uso que actualmente funcionan en Xamarin.Mac).

Hay dos áreas principales en las que AOT puede ayudar a una aplicación de Xamarin.Mac:

  • Mejores registros de bloqueo "nativos": si una aplicación de Xamarin.Mac se bloquea en código nativo, lo que es habitual al realizar llamadas no válidas a las API de Cocoa (por ejemplo, enviar un null a un método que no lo acepte), los registros de bloqueo nativos con marcos JIT son difíciles de analizar. Dado que los marcos JIT no tienen información de depuración, habrá varias líneas con desplazamientos hexadecimares y sin pistas de lo que estaba ocurriendo. AOT genera marcos con nombre "reales" y los seguimientos son mucho más fáciles de leer. Esto también significa que la aplicación Xamarin.Mac interactuará mejor con herramientas nativas como lldb e Instruments.
  • Mejor rendimiento del tiempo de inicio: para aplicaciones grandes de Xamarin.Mac, con un tiempo de inicio de varios segundos, la compilación JIT de todo el código puede tardar mucho tiempo. AOT hace esto por adelantado.

Habilitación de la compilación AOT

AOT está habilitado en Xamarin.Mac haciendo doble clic en el Nombre del proyecto en el Explorador de soluciones, navegando a Compilación de Mac y agregando--aot:[options] a los argumentos adicionales mmp : campo (donde[options] es una o varias opciones para controlar el tipo AOT, consulte a continuación). Por ejemplo:

Adición de AOT a argumentos mmp adicionales

Importante

Habilitar la compilación de AOT aumenta considerablemente el tiempo de compilación, a veces, hasta varios minutos, pero puede mejorar los tiempos de inicio de la aplicación en un promedio de 20 %. Como resultado, la compilación AOT solo se debe habilitar en las compilaciones de Versión de una aplicación de Xamarin.Mac.

Opciones de compilación AOT

Hay varias opciones diferentes que se pueden ajustar al habilitar la compilación AOT en una aplicación de Xamarin.Mac:

  • none - Sin compilación AOT. Esta es la configuración predeterminada.
  • all - AOT compila todos los ensamblados de MonoBundle.
  • core - AOT compila los ensamblados Xamarin.Mac, System y mscorlib.
  • sdk - AOT compila el Xamarin.Mac y los ensamblados de Bibliotecas de clases base (BCL).
  • |hybrid - Agregar esto a una de las opciones anteriores habilita AOT híbrido que permite la eliminación de IL, pero dará como resultado tiempos de compilación más largos.
  • + - incluye un único archivo para la compilación AOT.
  • - - Quita un único archivo de la compilación AOT.

Por ejemplo, --aot:all,-MyAssembly.dll habilitaría la compilación AOT en todos los ensamblados de MonoBundle excepto MyAssembly.dll y --aot:core|hybrid,+MyOtherAssembly.dll,-mscorlib.dll habilitaría híbrido, el código AOT incluiría MyOtherAssembly.dll y excluyendo .mscorlib.dll

Estático parcial registrar

Al desarrollar una aplicación de Xamarin.Mac, minimizar el tiempo entre completar un cambio y probarlo puede ser importante para cumplir los plazos de desarrollo. Las estrategias como la modularización de código base y pruebas unitarias pueden ayudar a reducir los tiempos de compilación, ya que reducen el número de veces que una aplicación requerirá una recompilación completa costosa.

Además, la novedad de Xamarin.Mac, Estático parcial Registrar (como pionero en Xamarin.iOS) puede reducir drásticamente los tiempos de inicio de una aplicación de Xamarin.Mac en la configuración de Depuración. Comprender cómo el uso del Estático parcial Registrar puede exprimir una mejora de casi 5x en el inicio de depuración tomará un poco de fondo sobre lo que es registrar, además de cuál es la diferencia entre estática y dinámica, y lo que hace esta versión "estática parcial".

Acerca de registrar

Subyacente a cualquier aplicación de Xamarin.Mac se encuentra el marco Cocoa de Apple y el runtime Objective-C. Crear un puente entre este "mundo nativo" y el "mundo administrado" de C# es la responsabilidad principal de Xamarin.Mac. Parte de esta tarea está controlada mediante registrar,que se ejecuta dentro del método NSApplication.Init (). Este es uno de los motivos por los que cualquier uso de las API de Cocoa en Xamarin.Mac requiere que NSApplication.Init sea llamado primero.

El trabajo de registrar es informar al runtime Objective-Cde la existencia de las clases de C# de la aplicación que derivan de clases como NSApplicationDelegate, NSView, NSWindow y NSObject. Esto requiere un examen de todos los tipos de la aplicación para determinar qué necesita ser registrado y qué elementos de cada tipo se van a informar.

Este examen se puede realizar dinámicamente, al inicio de la aplicación con reflexión o estáticamente, como paso del tiempo de compilación. Al seleccionar un tipo de registro, el desarrollador debe tener en cuenta lo siguiente:

  • El registro estático puede reducir drásticamente los tiempos de inicio, pero puede ralentizar significativamente los tiempos de compilación (normalmente más del doble que el tiempo de depuración de la compilación). Este será el valor predeterminado para las compilaciones de configuración de Versión.
  • El registro dinámico retrasa este trabajo hasta el inicio de la aplicación y omite la generación de código, pero este trabajo adicional puede crear una pausa notable (al menos dos segundos) en el inicio de la aplicación. Esto es especialmente notable en la depuración de compilaciones de configuración, que asumen como valor predeterminado el registro dinámico y cuya reflexión es más lenta.

El registro estático parcial, que se introdujo primero en Xamarin.iOS 8.13, ofrece al desarrollador lo mejor de ambas opciones. Al calcular previamente la información de registro de todos los elementos de Xamarin.Mac.dll y enviar esta información con Xamarin.Mac dentro de una biblioteca estática (que solo debe vincularse en el momento de la compilación), Microsoft ha quitado la mayor parte del tiempo de reflexión de la dinámica registrar sin afectar al tiempo de compilación.

Habilitación de la estática parcial registrar

La Estática parcial Registrar está habilitada en Xamarin.Mac haciendo doble clic en el Nombre del proyecto en el Explorador de soluciones, yendo a Compilación de Mac y agregando --registrar:static al campo Argumentos adicionales mmp:. Por ejemplo:

Adición de la estática registrar parcial a argumentos mmp adicionales

Recursos adicionales

Estas son algunas explicaciones más detalladas sobre cómo funcionan las cosas internamente: