iOS 应用体系结构

Xamarin.iOS 应用程序在 Mono 执行环境中运行,并使用完整的提前 (AOT) 编译将 C# 代码编译为 ARM 程序集语言。 这与 Objective-C 运行时并行运行。 这两个运行时环境都在类似 UNIX 的内核上运行,特别是 XNU,并向用户代码公开各种 API,使开发人员能够访问基础本机或托管系统。

下图显示了此体系结构的基本概述:

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

本机代码和托管代码:说明

针对 Xamarin 进行开发时,通常使用术语 本机和托管 代码。 托管代码是由 .NET Framework 公共语言运行时管理的代码,或在 Xamarin 的情况下由其执行:Mono 运行时。 这就是我们所说的中间语言。

本机代码是将在特定平台上本机运行的代码(例如, Objective-C 甚至是 ARM 芯片上的 AOT 编译代码)。 本指南探讨 AOT 如何将托管代码编译为本机代码,并说明 Xamarin.iOS 应用程序的工作原理,通过绑定充分利用 Apple 的 iOS API,同时也有权访问。NET 的 BCL 和复杂的语言,如 C# 。

AOT

编译任何 Xamarin 平台应用程序时,Mono C# (或 F#) 编译器将运行,并将 C# 和 F# 代码编译为 Microsoft 中间语言(MSIL)。 如果要在模拟器上运行 Xamarin.Android、Xamarin.Mac 应用程序甚至 Xamarin.iOS 应用程序, .NET 公共语言运行时 (CLR) 使用实时 (JIT) 编译器编译 MSIL。 在运行时,此代码编译为本机代码,可在应用程序的正确体系结构上运行。

但是,Apple 设置的 iOS 存在安全限制,禁止在设备上执行动态生成的代码。 为了确保遵守这些安全协议,Xamarin.iOS 改为使用“提前”(AOT)编译器来编译托管代码。 这会为设备生成本机 iOS 二进制文件((可选)通过 LLVM 进行优化,可在 Apple 基于 ARM 的处理器上部署。 下面展示了这一点如何组合在一起的粗略图:

A rough diagram of how this fits together

使用 AOT 存在许多限制,这些限制在“限制”指南中详细介绍。 它还通过减少启动时间和各种性能优化,对 JIT 进行了多项改进

了解代码如何从源代码编译到本机代码后,让我们看看 Xamarin.iOS 如何允许我们编写完全本机 iOS 应用程序

选择器

通过 Xamarin,我们有两个单独的生态系统:.NET 和 Apple,我们需要将这两个生态系统组合在一起,以便尽可能简化,以确保最终目标是一个流畅的用户体验。 在上面的部分中,我们了解了两个运行时的通信方式,你可能非常了解了“绑定”一词,该术语允许在 Xamarin 中使用本机 iOS API。 绑定在我们的 Objective-C 绑定 文档中进行了深入介绍,因此现在让我们来探讨 iOS 在后台的工作原理。

首先,必须有一种方法向 C# 公开 Objective-C ,这是通过选择器完成的。 选择器是发送到对象或类的消息。 通过Objective-Cobjc_msgSend函数完成此操作。 有关使用选择器的详细信息,请参阅 Objective-C 选择器 指南。 还必须有一种方法来向其中公开托管代码 Objective-C,这更为复杂,因为 Objective-C 不知道托管代码的任何内容。 为了解决此问题,我们使用 Registrars。 下一部分将更详细地介绍这些内容。

Registrars

如上所述提及,registrar向该代码公开托管代码Objective-C的代码。 它通过创建派生自 NSObject 的每个托管类的列表来执行此操作:

  • 对于所有未包装现有Objective-C类的类,它将创建一个新Objective-C类,该Objective-C类的成员镜像具有 [Export] 属性的所有托管成员。

  • 在每个 Objective-C 成员的实现中,会自动添加代码以调用镜像托管成员。

下面的伪代码演示了如何执行此操作的示例:

C# (托管代码)

 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

托管代码可以包含属性,[Register]以及[Export]registrar用于知道对象需要公开给Objective-C的属性。 此属性 [Register] 用于指定生成的 Objective-C 类的名称,以防默认生成的名称不适用。 从 NSObject 派生的所有类都将自动注册到 Objective-C。 必需 [Export] 属性包含一个字符串,该字符串是生成的 Objective-C 类中使用的选择器。

Xamarin.iOS 中有两种类型的 registrars 用法 – 动态和静态:

  • 动态 registrars – 动态 registrar 在运行时在程序集中注册所有类型的类型。 它通过使用运行时 API 提供的Objective-C函数来执行此操作。 因此,动态 registrar 启动速度较慢,但生成时间更快。 这是 iOS 模拟器的默认值。 本机函数(通常为 C),称为 trampolines,在使用动态 registrars时用作方法实现。 它们在不同体系结构之间有所不同。

  • 静态 registrars – 静态 registrar 在生成期间生成 Objective-C 代码,然后编译为静态库并链接到可执行文件。 这允许更快的启动,但在生成期间需要更长的时间。 这默认用于设备生成。 静态registrar还可以与 iOS 模拟器一起使用,方法是在项目的生成选项中作为mtouch属性传递--registrar:static,如下所示:

    Setting Additional mtouch arguments

有关 Xamarin.iOS 使用的 iOS 类型注册系统的详细信息,请参阅 类型 Registrar 指南。

应用程序启动

所有 Xamarin.iOS 可执行文件的入口点由调用 xamarin_main的函数提供,该函数初始化 mono。

根据项目类型,将执行以下操作:

  • 对于常规 iOS 和 tvOS 应用程序,将调用 Xamarin 应用提供的托管 Main 方法。 然后,此托管 Main 方法调用 UIApplication.Main,这是其 Objective-C入口点。 UIApplication.Main 是 's 方法的Objective-CUIApplicationMain绑定。
  • 对于扩展,调用 Apple 库提供的本机函数( NSExtensionMain 或(NSExtensionmain 对于 WatchOS 扩展)。 由于这些项目是类库,而不是可执行项目,因此没有要执行的托管 Main 方法。

所有这些启动序列都编译为静态库,然后链接到最终可执行文件,以便应用知道如何离开地面。

此时,应用已启动,Mono 正在运行,我们位于托管代码中,并且知道如何调用本机代码并被回调。 接下来,我们需要做的是开始添加控件并使应用交互。

Generator

Xamarin.iOS 包含每个 iOS API 的定义。 可以在 MaciOS github 存储库浏览其中任何一项。 这些定义包含具有属性的接口,以及任何必要的方法和属性。 例如,以下代码用于在 UIKit 命名空间中定义 UIToolbar。 请注意,它是具有多种方法和属性的接口:

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

    ...
}

在 Xamarin.iOS 中调用 btouch 的生成器采用这些定义文件,并使用 .NET 工具 将它们编译为临时程序集。 但是,此临时程序集不能用于调用 Objective-C 代码。 然后,生成器读取临时程序集并生成可在运行时使用的 C# 代码。 例如,如果将随机属性添加到定义.cs文件,则不会显示在输出的代码中。 生成器不知道它,因此 btouch 不知道在临时程序集中查找它以输出它。

创建Xamarin.iOS.dll后,mtouch 会将所有组件捆绑在一起。

在高级别上,它通过执行以下任务来实现此目的:

  • 创建应用捆绑结构。
  • 在托管程序集中复制。
  • 如果启用了链接,请运行托管链接器,通过撕裂未使用的部件来优化程序集。
  • AOT 编译。
  • 创建一个本机可执行文件,它输出一系列静态库(每个程序集)链接到本机可执行文件,以便本机可执行文件由启动器代码、 registrar 代码(如果静态)和 AOT 编译器的所有输出组成

有关链接器及其用法的详细信息,请参阅 链接器 指南。

总结

本指南探讨了 Xamarin.iOS 应用的 AOT 编译,并探讨了 Xamarin.iOS 及其与 Objective-C 深度的关系。