Xamarin.iOS の型 registrar
このドキュメントでは、Xamarin.iOS で使用される型登録システムについて説明します。
マネージド クラスとメソッドの登録
起動時に、Xamarin.iOS では以下が登録されます。
- Objective-C クラスとして [Register] 属性を持つクラス。
- Objective-C カテゴリとして [Category] 属性を持つクラス。
- Objective-C プロトコルとして [Protocol] 属性を持つインターフェイス。
- [Export] を持つメンバー。Objective-C がそれらにアクセスできるようになります。
たとえば、Xamarin.iOS アプリケーションで一般的なマネージド Main
メソッドについて考えてみましょう。
UIApplication.Main (args, null, "AppDelegate");
このコードでは、アプリケーションのデリゲート クラスとして AppDelegate
という型を使用するように Objective-C ランタイムに指示します。 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";
}
マネージド拡張メソッドはすべて静的にする必要がありますが、拡張メソッドの標準 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
を MyProtocol
というプロトコルと、そのプロトコルを実装する MyClass
というクラスとして、Objective-C にエクスポートします。
新しい登録システム
安定した 6.2.6 バージョンとベータ 6.3.4 バージョンから、新しい静的 registrar が追加されました。 7.2.1 バージョンでは、新しい registrar バージョンが既定値になりました。
この新しい登録システムでは、次の新機能が提供されます。
プログラマ エラーのコンパイル時検出:
- 同じ名前で登録されている 2 つのクラス。
- 同じセレクターに応答するためにエクスポートされた複数のメソッド
未使用のネイティブ コードの削除:
- 新しい登録システムでは、静的ライブラリで使用されるコードへの厳密な参照が追加され、ネイティブ リンカーが結果のバイナリから未使用のネイティブ コードを取り除くことを可能にします。 Xamarin のサンプル バインドでは、ほとんどのアプリケーションは少なくとも 300k 小さくなります。
NSObject
のジェネリック サブクラスのサポート; 詳細については、NSObject のジェネリックに関するページを参照してください。 さらに、新しい登録システムでは、以前は実行時にランダムな動作を引き起こした、サポートされていないジェネリック コンストラクトがキャッチされます。
新しい registrar によってキャッチされたエラー
新しい registrar によってキャッチされたエラーの例をいくつか以下に示します。
同じクラスで同じセレクターを複数回エクスポートする:
[Register] class MyDemo : NSObject { [Export ("foo:")] void Foo (NSString str); [Export ("foo:")] void Foo (string str) }
同じ Objective-C 名前を持つ複数のマネージド クラスをエクスポートする:
[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 以前を使っている場合は、Clang を使用するために追加の mtouch 引数に
--compiler:clang
を追加します (Xcode 5.0 以降では、Xamarin.iOS によって Clang が自動的に選択されます)。Xcode 4.6 (またはそれ以前) を使用する場合、エクスポートされた型名に非 ASCII 文字が含まれている場合は、GCC/G++ を選択する必要があります (これは、Xcode 4.6 に付属している Clang のバージョンでは、Objective-C コードの識別子内の非 ASCII 文字がサポートされていないためです)。 GCC を使用するために、追加の mtouch 引数に
--compiler:gcc
を追加します。
registrar の選択
プロジェクトの [iOS ビルド] 設定の追加の mtouch 引数に次のいずれかのオプションを追加することで、別のregistrar を選択できます。
--registrar:static
– デバイス ビルドの既定値--registrar:dynamic
– シミュレーター ビルドの既定値
Note
Xamarin の Classic API では、--registrar:legacystatic
や --registrar:legacydynamic
などの他のオプションがサポートされていました。 しかし、これらのオプションは Unified API ではサポートされていません。
古い登録システムの欠点
古い登録システムには次の欠点があります。
- サード パーティのネイティブ ライブラリに Objective-C クラスとメソッドへの (ネイティブ) 静的参照はありませんでした。つまり、ネイティブ リンカーに、実際に使用されていないサード パーティのネイティブ コードを削除するように依頼できませんでした (すべてが削除されるため)。 そのため、すべてのサード パーティのバインドで
-force_load libNative.a
を実行する必要がありました (または[LinkWith]
属性の同等のForceLoad=true
)。 - 警告なしで同じ Objective-C 名を持つ 2 つのマネージド型をエクスポートできました。 まれに、最終的に異なる名前空間に 2 つの
AppDelegate
クラスが含まれる場合がありました。 実行時の選択は完全にランダムでした (実際には、再構築さえされていないアプリの実行によって異なりました。これは、非常に不可解でイライラするデバッグ エクスペリエンスの場合です)。 - 同じ Objective-C シグネチャを持つ 2 つのメソッドをエクスポートできます。 さらに、Objective-C からの呼び出しはランダムでした (しかし、この問題は前のものほど一般的ではありませんでした。主に、このバグが実際に発生するのは、不適切なマネージド メソッドをオーバーライドする場合でした)。
- エクスポートされた一連のメソッドは、動的ビルドと静的ビルドで若干異なっていました。
- ジェネリック クラスのエクスポート時に正しく機能しません (実行時に行われる厳密なジェネリック実装はランダムであり、実質的に未確定の動作になります)。
新しい registrar: バインドに必要な変更
このセクションでは、新しい registrar バインドを操作するために行う必要があるバインドの変更について説明します。
プロトコルには [Protocol] 属性が必要
プロトコルには [Protocol]
属性が必要です。 そうしないと、次のようなネイティブ リンカー エラーが発生します。
Undefined symbols for architecture i386: "_OBJC_CLASS_$_ProtocolName", referenced from: ...
セレクターには有効な数のパラメーターが必要
すべてのセレクターでパラメーターの数を正しく示す必要があります。 以前は、これらのエラーは無視され、実行時の問題が発生する可能性がありました。
要するに、コロンの数はパラメーターの数と一致する必要があります。
- パラメーターなし:
foo
- 1 つのパラメーター:
foo:
- 2 つのパラメーター:
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 パラメーターを使用する
可変個引数関数では、[Export]
属性に対して IsVariadic
引数を使用する必要があります。
[Export ("variadicMethod:", IsVariadic = true)]
void VariadicMethod (NSObject first, IntPtr subsequent);
既存のシンボルにリンクする必要がある
ネイティブ ライブラリに存在しないクラスをバインドすることはできません。 ネイティブ ライブラリのクラスが削除または名前変更された場合は、必ず一致するようにバインドを更新してください。