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

原生和Managed程式代碼:說明

針對 Xamarin 進行開發時,通常會使用原生和 Managed 程式代碼等詞彙Managed 程式代碼是由 .NET Framework Common Language Runtime 所管理,或在 Xamarin 的案例中執行的程式代碼:Mono Runtime。 這就是我們所謂的中繼語言。

機器碼是在特定平臺上原生執行的程式代碼(例如, Objective-C 甚至是ARM晶片上的AOT編譯程式代碼)。 本指南會探索 AOT 如何將 Managed 程式代碼編譯成機器碼,並說明 Xamarin.iOS 應用程式的運作方式,透過使用系結充分利用 Apple 的 iOS API,同時也可以存取 。NET 的 BCL 和複雜的語言,例如 C#。

AOT

當您編譯任何 Xamarin 平臺應用程式時,Mono C# (或 F#) 編譯程式將會執行,並將 C# 和 F# 程式代碼編譯成 Microsoft Intermediate Language (MSIL)。 如果您正在模擬器上執行 Xamarin.Android、Xamarin.Mac 應用程式,甚至是 Xamarin.iOS 應用程式, .NET Common Language Runtime (CLR) 會使用 Just in Time (JIT) 編譯 MSIL。 在運行時間,這會編譯成原生程序代碼,其可在應用程式的正確架構上執行。

不過,Apple 所設定的 iOS 安全性限制不允許在裝置上執行動態產生的程式代碼。 為了確保我們遵守這些安全通訊協定,Xamarin.iOS 會改用「提前」(AOT)編譯程式來編譯 Managed 程式代碼。 這會產生原生 iOS 二進位檔,選擇性地針對裝置使用 LLVM 優化,可在 Apple 的 ARM 處理器上部署。 下圖說明這如何結合在一起:

A rough diagram of how this fits together

使用 AOT 有一些限制,如限制指南中詳述。 它也透過縮短啟動時間和各種效能優化,提供 JIT 的一些改善

既然我們已經探索如何從原始程式碼編譯程式代碼到機器碼,讓我們看看 Xamarin.iOS 如何讓我們撰寫完全原生的 iOS 應用程式

選取器

使用 Xamarin 時,我們有兩個不同的生態系統.NET 和 Apple,我們需要整合在一起,才能盡可能簡化,以確保最終目標是順暢的用戶體驗。 我們在上一節中看到這兩個運行時間的通訊方式,您可能非常瞭解「系結」一詞,這可讓原生 iOS API 用於 Xamarin。 系結會在我們的 Objective-C 系結 檔中深入說明,因此現在讓我們來探索 iOS 在幕後的運作方式。

首先,必須有一種方式可以公開 Objective-C 至 C#,這是透過 Selectors 完成的。 選取器是傳送至物件或類別的訊息。 透過Objective-Cobjc_msgSend函式來完成此作業。 如需使用選取器的詳細資訊,請參閱 Objective-C 選取器 指南。 此外,還必須有一種方法,才能將Managed程式碼公開至 Objective-C,這更為複雜,因為 Objective-C 對Managed程式碼沒有任何瞭解。 為了解決這個問題,我們使用 Registrars。 下一節會更詳細地說明這些內容。

Registrars

如上所述, registrar 是將Managed程式代碼公開給 Objective-C的程序代碼。 其方式是建立衍生自 NSObject 的每個 Managed 類別列表:

  • 對於未包裝現有 Objective-C 類別的所有類別,它會建立新的 Objective-C 類別,其中 Objective-C 成員會鏡像具有 [Export] 屬性的所有Managed成員。

  • 在每個 Objective-C 成員的實作中,會自動新增程式代碼來呼叫鏡像受控成員。

下列虛擬程式代碼示範如何完成此作業的範例:

C# (Managed 程式代碼)

 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

Managed 程式代碼可以包含 屬性, [Register] 以及 [Export], registrar 用來知道對象必須公開給 Objective-C。 屬性 [Register] 是用來指定所 Objective-C 產生類別的名稱,以防預設產生的名稱不適用。 所有衍生自 NSObject 的類別都會自動向 Objective-C註冊。 必要的 [Export] 屬性包含字串,這是在產生的 Objective-C 類別中使用的選取器。

Xamarin.iOS 中有兩種類型的 registrars 使用 – 動態和靜態:

  • 動態 – 動態registrarsregistrar會在運行時間對元件中的所有類型進行註冊。 它會使用 運行時間 API 所提供的Objective-C函式來執行此作業。 因此,動態 registrar 的啟動速度較慢,但建置時間較快。 這是 iOS 模擬器的預設。 原生函式 (通常是在 C 中),稱為 trampolines,會在使用動態 registrars時做為方法實作。 它們在不同的架構之間有所不同。

  • Static – 靜態registrarsregistrar會在Objective-C建置期間產生程式代碼,然後編譯成靜態庫並連結至可執行檔。 這允許更快速的啟動,但在建置期間需要較長的時間。 根據預設,這會用於裝置組建。 靜態 registrar 也可以與 iOS 模擬器搭配使用,方法是在專案的建置選項中傳遞 --registrar:static 作為 mtouch 屬性,如下所示:

    Setting Additional mtouch arguments

如需 Xamarin.iOS 所使用之 iOS 類型註冊系統之細節的詳細資訊,請參閱 類型 Registrar 指南。

應用程式啟動

所有 Xamarin.iOS 可執行文件的進入點是由稱為 xamarin_main的函式提供,其會初始化 mono。

視項目類型而定,完成下列動作:

  • 針對一般 iOS 和 tvOS 應用程式,會呼叫 Xamarin 應用程式所提供的 Managed Main 方法。 這個 Managed Main 方法接著會呼叫 UIApplication.Main,這是 的 Objective-C進入點。 UIApplication.Main 是 方法的UIApplicationMain系結Objective-C。
  • 針對延伸模組,會呼叫Apple連結庫所提供的原生函式或 NSExtensionMainNSExtensionmain 適用於 WatchOS 擴充功能)。 由於這些專案是類別庫,而不是可執行的專案,因此沒有要執行的ManagedMain方法。

此啟動序列全都會編譯成靜態庫,然後連結至最終可執行檔,讓應用程式知道如何離開地面。

此時,我們的應用程式已啟動,Mono 正在執行中,我們位於 Managed 程式代碼中,且知道如何呼叫機器碼並呼叫回呼。 我們需要做的下一件事是實際開始新增控件,並讓應用程式成為互動式。

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 會將所有元件組合在一起。

概括而言,它會藉由執行下列工作來達成此目的:

  • 建立應用程式套件組合結構。
  • 在受控元件中複製。
  • 如果啟用連結功能,請執行 Managed 連結器,藉由將未使用的元件撕掉,將元件優化。
  • AOT 編譯。
  • 建立原生可執行檔,其會輸出連結至原生可執行檔的一系列靜態連結庫(每個元件各一個),讓原生可執行檔包含啟動器程式代碼、 registrar 程序代碼(如果為 static),以及 AOT 編譯程式的所有輸出

如需連結器及其使用方式的詳細資訊,請參閱 連結器 指南。

摘要

本指南探討 Xamarin.iOS 應用程式的 AOT 編譯,並深入探索 Xamarin.iOS 及其關聯 Objective-C 性。