Xamarin.Mac 体系结构
本指南探讨 Xamarin.Mac 及其与低级别的关系 Objective-C 。 它介绍编译、选择器、 registrars应用启动和生成器等概念。
概述
Xamarin.Mac 应用程序在 Mono 执行环境中运行,并使用 Xamarin 的编译器编译为中间语言(IL),后者随后在运行时编译为本机代码。 这与 Objective-C 运行时并行运行。 这两个运行时环境都在类似 UNIX 的内核上运行,特别是 XNU,并向用户代码公开各种 API,使开发人员能够访问基础本机或托管系统。
下图显示了此体系结构的基本概述:
本机和托管代码
针对 Xamarin 进行开发时, 通常使用本机 代码和 托管 代码术语。 托管代码是由 .NET Framework 公共语言运行时管理的代码,或在 Xamarin 的情况下由其执行:Mono 运行时。
本机代码是将在特定平台上本机运行的代码(例如, Objective-C 甚至是 ARM 芯片上的 AOT 编译代码)。 本指南介绍了托管代码如何编译为本机代码,并介绍了 Xamarin.Mac 应用程序的工作原理,通过绑定充分利用 Apple 的 Mac API,同时也有权访问。NET 的 BCL 和复杂的语言,如 C# 。
要求
以下是通过 Xamarin.Mac 开发 macOS 应用程序所需的条件:
- 运行 macOS Sierra(10.12)或更高版本的 Mac。
- 最新版本的 Xcode (从 App Store 安装)
- 最新版本的 Xamarin.Mac 和 Visual Studio for Mac
运行通过 Xamarin.Mac 创建的 Mac 应用程序具有以下系统要求:
- 运行 Mac OS X 10.7 或更高版本的 Mac。
编译
编译任何 Xamarin 平台应用程序时,Mono C# (或 F#) 编译器将运行,并将 C# 和 F# 代码编译为 Microsoft 中间语言(MSIL 或 IL)。 然后,Xamarin.Mac 在运行时使用 实时 (JIT) 编译器来编译本机代码,从而根据需要在正确的体系结构上执行。
这与使用 AOT 编译的 Xamarin.iOS 形成鲜明对比。 使用 AOT 编译器时,将在生成时编译所有程序集及其所有方法。 使用 JIT 时,编译仅按需执行所执行的方法。
使用 Xamarin.Mac 应用程序时,Mono 通常嵌入到应用捆绑包中(也称为 Embedded Mono)。 使用经典 Xamarin.Mac API 时,应用程序可以改用 系统 Mono,但在统一 API 中不受支持。 系统 Mono 是指已在操作系统中安装的 Mono。 在应用程序启动时,Xamarin.Mac 应用将使用此应用。
选择器
通过 Xamarin,我们有两个单独的生态系统:.NET 和 Apple,我们需要将这两个生态系统组合在一起,以便尽可能简化,以确保最终目标是一个流畅的用户体验。 在上面的部分中,我们已经了解了两个运行时的通信方式,你可能非常了解了“绑定”一词,该术语允许在 Xamarin 中使用本机 Mac API。 绑定在 Objective-C 绑定文档中进行了深入介绍,因此,现在让我们来了解 Xamarin.Mac 在后台的工作原理。
首先,必须有一种方法向 C# 公开 Objective-C ,这是通过选择器完成的。 选择器是发送到对象或类的消息。 通过Objective-Cobjc_msgSend函数完成此操作。 有关使用选择器的详细信息,请参阅 iOS Objective-C 选择器 指南。 还必须有一种方法来向其中公开托管代码 Objective-C,这更为复杂,因为 Objective-C 不知道托管代码的任何内容。 为了解决此问题,我们使用一个 registrar。 下一部分更详细地介绍了这一点。
Registrar
如上所述提及,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 C# MyFunc method in MyViewController
}
@end
托管代码可以包含属性,[Register]
以及[Export]
registrar用于知道对象需要公开给Objective-C的属性。 [Register] 属性用于指定生成的 Objective-C 类的名称,以防默认生成的名称不适用。 从 NSObject 派生的所有类都将自动注册到 Objective-C。 必需的 [Export] 属性包含一个字符串,该字符串是生成的 Objective-C 类中使用的选择器。
Xamarin.Mac 中有两种类型的 registrars 使用 – 动态和静态:
- 动态 registrars – 这是所有 Xamarin.Mac 版本的默认值 registrar 。 registrar动态在运行时对程序集中的所有类型进行注册。 它通过使用运行时 API 提供的 Objective-C函数来执行此操作。 因此,动态 registrar 启动速度较慢,但生成时间更快。 本机函数(通常为 C),称为 trampolines,在使用动态 registrars时用作方法实现。 它们在不同体系结构之间有所不同。
- 静态 registrars – 静态 registrar 在生成期间生成 Objective-C 代码,然后编译为静态库并链接到可执行文件。 这允许更快的启动,但在生成期间需要更长的时间。
应用程序启动
Xamarin.Mac 启动逻辑将有所不同,具体取决于是使用嵌入式还是系统 Mono。 若要查看 Xamarin.Mac 应用程序启动的代码和步骤,请参阅 xamarin-macios 公共存储库中的启动头 文件。
Generator
Xamarin.Mac 包含每个 Mac API 的定义。 可以在 MaciOS github 存储库中浏览其中任何一项。 这些定义包含具有属性的接口,以及任何必要的方法和属性。 例如,以下代码用于在 AppKit 命名空间中定义 NSBox。 请注意,它是具有多种方法和属性的接口:
[BaseType (typeof (NSView))]
public interface NSBox {
…
[Export ("borderRect")]
CGRect BorderRect { get; }
[Export ("titleRect")]
CGRect TitleRect { get; }
[Export ("titleCell")]
NSObject TitleCell { get; }
[Export ("sizeToFit")]
void SizeToFit ();
[Export ("contentViewMargins")]
CGSize ContentViewMargins { get; set; }
[Export ("setFrameFromContentFrame:")]
void SetFrameFromContentFrame (CGRect contentFrame);
…
}
在 Xamarin.Mac 中调用 bmac
的生成器采用这些定义文件,并使用 .NET 工具将它们编译为临时程序集。 但是,此临时程序集不能用于调用 Objective-C 代码。 然后,生成器读取临时程序集并生成可在运行时使用的 C# 代码。 例如,如果将随机属性添加到定义.cs文件,则不会显示在输出的代码中。 生成器不知道它,因此 bmac
不知道在临时程序集中查找它以输出它。
创建Xamarin.Mac.dll后,打包程序 mmp
会将所有组件捆绑在一起。
在高级别上,它通过执行以下任务来实现此目的:
- 创建应用捆绑结构。
- 在托管程序集中复制。
- 如果启用了链接,请运行托管链接器,通过删除未使用的部件来优化程序集。
- 创建启动器应用程序,并在静态模式下与代码一起 registrar 链接启动器代码。
然后,它将作为用户生成过程的一部分运行,它将用户代码编译为引用的程序集Xamarin.Mac.dll并运行 mmp
以使其成为包
有关链接器及其使用方式的更多详细信息,请参阅 iOS 链接器 指南。
总结
本指南介绍了 Xamarin.Mac 应用的编译,并探讨了 Xamarin.Mac 及其与 Xamarin.Mac 的关系 Objective-C。