Xamarin.iOS 的類型registrar
本文件說明 Xamarin.iOS 所使用的類型註冊系統。
註冊 Managed 類別和方法
在啟動期間,Xamarin.iOS 將會註冊:
- 具有 [Register] 屬性做為 Objective-C 類別的類別。
- 具有 [Category] 屬性做為 Objective-C 類別的類別。
- 具有 [Protocol] 屬性做為 Objective-C 通訊協議的介面。
- 具有 [導出] 的成員,可讓您 Objective-C 存取它們。
例如,請考慮 Xamarin.iOS 應用程式中常見的 Managed Main
方法:
UIApplication.Main (args, null, "AppDelegate");
此程式代碼會 Objective-C 告知運行時間使用稱為 AppDelegate
的型別作為應用程式的委派類別。 Objective-C若要讓運行時間能夠建立 C# AppDelegate
類別的實例,則必須註冊該類別。
Xamarin.iOS 會在運行時間(動態註冊)或編譯時間(靜態註冊)自動執行註冊。
動態註冊會在啟動時使用反映來尋找所有要註冊的類別和方法,並將其傳遞至 Objective-C 運行時間。 模擬器組建預設會使用動態註冊。
靜態註冊會在編譯時期檢查應用程式所使用的元件。 它會決定要向 註冊 Objective-C 的類別和方法,併產生內嵌至您的二進位檔的對應。 然後,在啟動時,它會向 Objective-C 運行時間註冊對應。 靜態註冊用於裝置組建。
類別
從 Xamarin.iOS 8.10 開始,可以使用 C# 語法建立 Objective-C 類別。
若要建立類別,請使用 [Category]
屬性並指定要擴充的類型。 例如,下列程式代碼會 NSString
擴充 :
[Category (typeof (NSString))]
每個類別的方法都有屬性 [Export]
,使其可供 Objective-C 運行時間使用:
[Export ("today")]
public static string Today ()
{
return "Today";
}
所有 Managed 擴充方法都必須是靜態的,但可以使用擴充方法的標準 C# 語法來建立 Objective-C 實例方法:
[Export ("toUpper")]
public static string ToUpper (this NSString self)
{
return self.ToString ().ToUpper ();
}
擴充方法的第一個自變數是叫用方法的實例:
[Category (typeof (NSString))]
public static class MyStringCategory
{
[Export ("toUpper")]
static string ToUpper (this NSString self)
{
return self.ToString ().ToUpper ();
}
}
這個範例會將原生 toUpper
實例方法新增至 NSString
類別。 這個方法可以從 呼叫 Objective-C:
[Category (typeof (UIViewController))]
public static class MyViewControllerCategory
{
[Export ("shouldAutoRotate")]
static bool GlobalRotate ()
{
return true;
}
}
通訊協定
從 Xamarin.iOS 8.10 開始,屬性的 [Protocol]
介面將會匯出為 Objective-C 通訊協定:
[Protocol ("MyProtocol")]
interface IMyProtocol
{
[Export ("method")]
void Method ();
}
class MyClass : IMyProtocol
{
void Method ()
{
}
}
此程式代碼會匯出 IMyProtocol
為 Objective-C 稱為 MyProtocol
的通訊協定,而稱為 MyClass
的類別會實作通訊協定。
新的註冊系統
從穩定 6.2.6 版和 beta 6.3.4 版本開始,我們新增了新的靜態 registrar。 在 7.2.1 版本中,我們會將新的 registrar 設為預設值。
這個新的註冊系統提供下列新功能:
程式設計人員錯誤的編譯時間偵測:
- 兩個類別會以相同名稱註冊。
- 匯出多個方法以回應相同的選取器
移除未使用的機器碼:
- 新的註冊系統會將強式參考新增至靜態連結庫中所使用的程式代碼,讓原生連結器從產生的二進位檔中去除未使用的機器碼。 在 Xamarin 的範例系結上,大部分的應用程式至少會變小 30 萬。
支援的
NSObject
泛型子類別;如需詳細資訊,請參閱 NSObject 泛型 。 此外,新的註冊系統會攔截先前在運行時間造成隨機行為的不支援泛型建構。
新攔截的錯誤 registrar
以下是新 registrar擷取的錯誤範例。
在同一個類別中多次匯出相同的選取器:
[Register] class MyDemo : NSObject { [Export ("foo:")] void Foo (NSString str); [Export ("foo:")] void Foo (string str) }
匯出多個具有相同 Objective-C 名稱的 Managed 類別:
[Register ("Class")] class MyClass : NSObject {} [Register ("Class")] class YourClass : NSObject {}
匯出泛型方法:
[Register] class MyDemo : NSObject { [Export ("foo")] void Foo<T> () {} }
新的限制 registrar
一些要記住的新 registrar專案:
某些第三方連結庫必須更新,才能使用新的註冊系統。 如需詳細資訊,請參閱下方的必要修改。
短期缺點也是使用 Accounts 架構時必須使用 Clang (這是因為 Apple 的 accounts.h 標頭只能由 Clang 編譯)。 如果您使用 Xcode 4.6 或更早版本,請新增
--compiler:clang
至其他 mtouch 自變數以使用 Clang(Xamarin.iOS 會自動在 Xcode 5.0 或更新版本中選取 Clang。如果使用 Xcode 4.6(或更早版本),則如果導出的類型名稱包含非 ASCII 字元,則必須選取 GCC/G++ (這是因為隨附於 Xcode 4.6 的 Clang 版本不支援程式代碼中識別符內的 Objective-C 非 ASCII 字元)。 將 新增
--compiler:gcc
至其他 mtouch 自變數以使用 GCC。
選取 registrar
您可以將下列其中一個選項新增至專案 iOS 組建設定中的其他 mtouch 自變數,以選取不同的registrar選項:
--registrar:static
– 裝置組建的預設值--registrar:dynamic
– 模擬器組建的預設值
注意
Xamarin 的傳統 API 支援其他選項,例如 --registrar:legacystatic
與 --registrar:legacydynamic
。 不過,統一 API 不支援這些選項。
舊註冊制度中的缺點
舊的註冊系統有下列缺點:
- 第三方原生連結庫中沒有類別和方法的靜態參考 Objective-C ,這表示我們無法要求原生連結器移除實際未使用的第三方原生程式代碼(因為會移除所有專案)。 這是因為
-force_load libNative.a
每個第三方系結必須執行 (或 屬性中的[LinkWith]
對等ForceLoad=true
專案)。 - 您可以匯出兩個具有相同名稱且沒有警告的 Objective-C Managed 類型。 罕見的案例最後會有兩
AppDelegate
個不同命名空間中的類別。 在運行時間,它會完全隨機挑選哪一個應用程式(事實上,它因非常令人費解和令人沮喪的偵錯體驗而產生不同)。 - 您可以匯出兩個具有相同 Objective-C 簽章的方法。 同樣地,從中呼叫 Objective-C 哪一個是隨機的(但這個問題不像前一個一樣常見,主要是因為實際遇到這個 Bug 的唯一方法是覆寫不幸的 Managed 方法)。
- 匯出的方法集合在動態和靜態組建之間稍有不同。
- 匯出泛型類別時,它無法正常運作(運行時間執行的確切泛型實作會是隨機的,實際上會導致無法確定的行為)。
新增 registrar:系結的必要變更
本節描述必須進行的系結變更,才能使用新的 registrar。
通訊協議必須具有 [Protocol] 屬性
通訊協議現在必須具有 [Protocol]
屬性。 如果您未這樣做,您將會產生原生連結器錯誤,例如:
Undefined symbols for architecture i386: "_OBJC_CLASS_$_ProtocolName", referenced from: ...
選取器必須有有效的參數數目
所有選取器都必須正確指出參數數目。 先前會忽略這些錯誤,並可能導致運行時間問題。
簡言之,冒號數目必須符合參數數目:
- 沒有參數:
foo
- 一個參數:
foo:
- 兩個參數:
foo:parameterName2:
下列用法不正確:
// Invalid: export takes no arguments, but function expects one
[Export ("apply")]
void Apply (NSObject target);
// Invalid: exported as taking an argument, but the managed version does not have one:
[Export ("display:")]
void Display ();
在導出中使用IsVariadic 參數
Variadic 函式必須使用 IsVariadic
屬性的 [Export]
自變數:
[Export ("variadicMethod:", IsVariadic = true)]
void VariadicMethod (NSObject first, IntPtr subsequent);
必須連結至現有的符號
無法系結原生連結庫中不存在的類別。 如果類別已從原生連結庫中移除或重新命名,請務必更新系結以符合。