Objective-C ライブラリのバインド
Xamarin.iOS または Xamarin.Mac を使用する場合は、サード パーティ製 Objective-C ライブラリを使用する必要がある場合があります。 このような状況では、Xamarin のバインド プロジェクトを使用すると、ネイティブ Objective-C ライブラリへの C# バインドを作成できます。 このプロジェクトでは、iOS API および Mac API を C# に取り込むために使うものと同じツールを使います。
このドキュメントでは、Objective-C API をバインドする方法について説明します。C API のみをバインドする場合は、標準の .NET メカニズムである、P /Invoke フレームワークを使用する必要があります。 C ライブラリを静的にリンクする方法の詳細については、「ネイティブ ライブラリのリンク」 ページを参照してください。
コンパニオンのバインドの種類のリファレンス ガイドを参照してください。 さらに、内部での動作の詳細については、「バインディングの概要」を参照してください。
バインドは、iOS ライブラリにも、Mac ライブラリにもビルドできます。 このページでは、iOS バインドで作業する方法について説明しますが、Mac のバインドも非常に似ています。
iOS 用サンプル コード
iOS バインド サンプル プロジェクトでバインドを試すことができます。
作業の開始
バインドを作成する最も簡単な方法は、Xamarin.iOS バインド プロジェクトを作成することです。 Visual Studio for Mac で、プロジェクト タイプに [iOS] > [ライブラリ] > [バインド ライブラリ] を選択することで可能です。
生成されたプロジェクトには、編集できる小さなテンプレートが含まれており、ApiDefinition.cs
、StructsAndEnums.cs
の 2 ファイルが含まれています。
ApiDefinition.cs
で API コントラクトを定義します。これは、基になる Objective-C API を C# に投影する方法を記述するファイルです。 このファイルの構文と内容を、このドキュメントのメイン トピックとして扱います。内容は、C# インターフェイスと C# デリゲート宣言に限定されています。 StructsAndEnums.cs
ファイルは、インターフェイスとデリゲートに必要な定義を入力するファイルです。 これには、コードで使用できる列挙値と構造体が含まれます。
API のバインド
包括的なバインドを行うには、Objective-C API の定義を理解し、.NET Framework の設計ガイドラインを理解してください。
ライブラリをバインドするには、通常、API 定義ファイルから始めます。 端的には、API 定義ファイルは、バインドを実行するのに役立ついくつかの属性で注釈が付けられた C# インターフェイスを含む C# ソース ファイルです。 このファイルは、C# と Objective-C の間のコントラクトを定義するファイルです。
たとえば、これはライブラリの簡単な API ファイルです。
using Foundation;
namespace Cocos2D {
[BaseType (typeof (NSObject))]
interface Camera {
[Static, Export ("getZEye")]
nfloat ZEye { get; }
[Export ("restore")]
void Restore ();
[Export ("locate")]
void Locate ();
[Export ("setEyeX:eyeY:eyeZ:")]
void SetEyeXYZ (nfloat x, nfloat y, nfloat z);
[Export ("setMode:")]
void SetMode (CameraMode mode);
}
}
上記のサンプルでは、NSObject
基本データ型 (Foundation.NSObject
を由来とする型) から派生した、静的プロパティ ZEye
を定義する Cocos2D.Camera
というクラスを定義します。2 つのメソッドは引数を受け取らず、1 つのメソッドは 3 つの引数を受け取ります。
API ファイルの形式と使用できる属性の詳細については、以下の「API 定義ファイル」で説明します。
完全なバインドを生成するには、通常、次の 4 つのコンポーネントを処理します。
- API 定義ファイル (テンプレートの
ApiDefinition.cs
)。 - 省略可能: API 定義ファイルに必要なすべての列挙型、型、構造体 (テンプレートの
StructsAndEnums.cs
)。 - 省略可能: 生成されたバインディングを拡張したり、より C# フレンドリーな API (プロジェクトに追加する任意の C# ファイル) を提供したりすることのできる追加のソース。
- バインドするネイティブ ライブラリ。
このチャートは、ファイル間の関係を示しています。
API 定義ファイルには、名前空間とインターフェイス定義 (インターフェイスに含めることができるメンバーを含む) のみが含まれるので、クラス、列挙型、デリゲート、または構造体を含めることはできません。 端的には、API 定義ファイルは、API の生成に使用されるコントラクトです。
列挙型やサポート クラスなど、必要な追加のコードは別のファイルでホストする必要があります。上記の例では、"CameraMode" は CS ファイルに存在せず、別のファイルでホストする必要がある列挙値です。たとえば、StructsAndEnums.cs
の場合:
public enum CameraMode {
FlyOver, Back, Follow
}
APIDefinition.cs
ファイルは StructsAndEnum
クラスと組み合わされ、ライブラリのコア バインディングを生成するために使用されます。 結果のライブラリをそのまま使用できますが、通常は、結果のライブラリを調整して、ユーザーの利便性のためにいくつかの C# 機能を追加することになります。 たとえば、ToString()
メソッドの実装、C# インデクサーの提供、一部のネイティブ型との間の暗黙的な変換の追加、一部のメソッドの厳密に型指定されたバージョンの提供などがあります。 これらの機能強化は、追加の C# ファイルに格納されます。 C# ファイルをプロジェクトに追加するだけで、このビルド プロセスに含めることができます。
これは、Extra.cs
ファイルにコードを実装する方法を示しています。 これらは、ApiDefinition.cs
および StructsAndEnums.cs
のコア バインディングの組み合わせから生成される部分クラスを拡張するものなので、部分クラスを使用することになります。
public partial class Camera {
// Provide a ToString method
public override string ToString ()
{
return String.Format ("ZEye: {0}", ZEye);
}
}
ライブラリをビルドすると、ネイティブ バインディングが生成されます。
このバインドを完了するには、ネイティブ ライブラリをプロジェクトに追加する必要があります。 これを行うには、Finder からソリューション エクスプローラーにネイティブ ライブラリをドラッグ アンド ドロップするか、プロジェクトを右クリックし、[追加]>[ファイルの追加] を選んだ後ネイティブ ライブラリを選択して、プロジェクトにネイティブ ライブラリを追加します。 慣例により、ネイティブ ライブラリはの先頭は "lib" という単語で、最後は拡張子 ".a" です。 これを行うと、Visual Studio for Mac によって 2 つのファイルが追加されます。.a ファイルと、ネイティブ ライブラリに含まれる内容に関する情報を含む自動的に設定された C# ファイルです。
libMagicChord.linkwith.cs
ファイルには、このライブラリを使用する方法に関する情報が含まれており、このバイナリを結果の DLL ファイルにパッケージ化するように IDE に指示します。
using System;
using ObjCRuntime;
[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]
使用方法に関する全体的な詳細 [LinkWith]
属性については、「バインディングの種類のリファレンス ガイド」を参照してください。
プロジェクトをビルドすると、バインドとネイティブ ライブラリの両方を含む MagicChords.dll
ファイルが作成されます。 このプロジェクトまたは結果の DLL は、他の開発者が独自に使用するために配布することができます。
列挙値、デリゲート定義、またはその他の型が必要な場合があります。 それらは単なるコントラクトであるため、API 定義ファイルには配置しないでください
API 定義ファイル
API 定義ファイルは、複数のインターフェイスで構成されています。 API 定義内のインターフェイスはクラス宣言に変換され、クラスの基底クラスを指定するために、[BaseType]
属性で修飾する必要があります。
コントラクト定義にクラスを使用せず、インターフェイスを使用した理由を疑問に思うかもしれません。 インターフェイスを使用した理由は、API 定義ファイルでメソッド本体を指定したり、例外をスローしたり意味のある値を返したりする必要がある本文を指定しなくても、メソッドのコントラクトを記述できるためです。
しかし、インターフェイスをクラスを生成するためのスケルトンとして使用するため、バインディングを駆動する属性を使用してコントラクトのさまざまな部分を装飾する必要がありました。
メソッドのバインド
実行できる最も簡単なバインドは、メソッドをバインドすることです。 C# の名前付け規則を使用してインターフェイスのメソッドを宣言し、そのメソッドを [Export] 属性で装飾します。[Export]
属性。 この [Export]
属性により、C# 名と Xamarin.iOS ランタイムの Objective-C 名がリンクされます。 - [Export]
属性のパラメーターは Objective-C セレクターの名前です。 次に例をいくつか示します。
// A method, that takes no arguments
[Export ("refresh")]
void Refresh ();
// A method that takes two arguments and return the result
[Export ("add:and:")]
nint Add (nint a, nint b);
// A method that takes a string
[Export ("draw:atColumn:andRow:")]
void Draw (string text, nint column, nint row);
上記のサンプルは、インスタンス メソッドをバインドする方法を示しています。 静的メソッドをバインドするには、次のように [Static]
属性を使用する必要があります。
// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();
これが必要な理由は、コントラクトがインターフェイスの一部であり、インターフェイスには静的宣言とインスタンス宣言の概念がないので、属性にもう一度頼る必要があるためです。 バインドから特定のメソッドを非表示にする場合は、[Internal]
属性でメソッドを装飾します。
btouch-native
コマンドでは、参照パラメーターが null でないかどうかのチェックが導入されます。 特定のパラメーターに対して null 値を許可する場合は、次のように、パラメーターに [NullAllowed]
属性を使用します。
[Export ("setText:")]
string SetText ([NullAllowed] string text);
参照型をエクスポートする場合は、[Export]
キーワードを使用して、割り当てセマンティクスを指定することもできます。 これは、データが漏えいしないようにするために必要です。
バインディングのプロパティ
メソッドと同様に、 Objective-C プロパティは [Export]
属性を使用し、C# プロパティに直接マップします。 メソッドと同様に、プロパティは [Static]
および [Internal]
属性で装飾できます。
内部プロパティで [Export]
属性を使用すると、btouch-native は実際にはゲッターとセッターの 2 つのメソッドをバインドします。 エクスポートするために指定した名前がベース名となり、単語 "set" を置くとセッターが計算され、ベース名の最初の 1 文字が大文字になり、セレクターに引数が指定されます。 つまり、[Export ("label")]
がプロパティに適用されると、"label" メソッドと "setLabel:" Objective-C メソッドが実際にバインドされます。
Objective-C プロパティが上記のパターンに従っておらず、名前が手動で上書きされる場合があります。 このような場合は、バインディングの生成方法を制御するには、ゲッターまたはセッターに [Bind]
属性を使用します。次に例を示します。
[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }
これにより、"isMenuVisible" と "setMenuVisible:" がバインドされます。 必要に応じて、次の構文を使用してプロパティをバインドできます。
[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
[Export ("name")]
string Name();
[Export("setName:")]
void SetName(string name);
}
ゲッターとセッターは、上記の name
および setName
バインドと同様に、明示的に定義されています。
[Static]
を使用する静的プロパティのサポートに加えて、[IsThreadStatic]
を使用して thread-static プロパティを装飾することもできます。次に例を示します。
[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }
メソッドで一部のパラメーターに [NullAllowed]
でフラグ設定できるのと同様に、[NullAllowed]
をプロパティに適用し、そのプロパティには null が有効な値であることを示すことができます。次に例を示します。
[Export ("text"), NullAllowed]
string Text { get; set; }
[NullAllowed]
パラメーターは、セッターで直接指定することもできます。
[Export ("text")]
string Text { get; [NullAllowed] set; }
カスタム コントロールのバインドに関する注意事項
カスタム コントロールのバインドを設定するときは、次の注意事項を考慮してください。
- バインド プロパティは静的である必要があります - プロパティのバインドを定義するときは、
[Static]
属性を使用する必要があります。 - プロパティ名は正確に一致する必要があります - プロパティのバインドに使用される名前は、カスタム コントロール内のプロパティの名前と正確に一致する必要があります。
- プロパティの型は正確に一致する必要があります - プロパティのバインドに使用される変数の型は、カスタム コントロール内のプロパティの型と正確に一致する必要があります。
- ブレークポイントとゲッター/セッター - プロパティのゲッター メソッドまたはセッター メソッドに配置されたブレークポイントはヒットしません。
- コールバックの監視 - カスタム コントロールのプロパティ値の変更に関する通知を受けるには、監視コールバックを使用する必要があります。
上記の注意事項のいずれかを怠ると、実行時にバインディングが暗黙的に失敗する可能性があります。
Objective-C 変更可能なパターンとプロパティ
Objective-C フレームワークでは、一部のクラスが変更可能なサブクラスで変更できないイディオムを使用します。 たとえば、NSString
は変更できないバージョンですが、NSMutableString
は変更可能なサブクラスです。
これらのクラスでは一般に、不変の基底クラスにゲッターがあり、セッターはないプロパティが含まれています。 また、変更可能なバージョンではセッターを導入します。 これは C# では実際には不可能であるため、このイディオムを C# で動作するイディオムにマップする必要がありました。
これを C# にマップする方法は、基底クラスにゲッターとセッターの両方を追加します。ただし、セッターには xxx をフラグ付けします。[NotImplemented]
属性。
次に、変更可能なサブクラスでは、プロパティに [Override]
属性を使用して、そのプロパティが実際に親の動作をオーバーライドするようにします。
例:
[BaseType (typeof (NSObject))]
interface MyTree {
string Name { get; [NotImplemented] set; }
}
[BaseType (typeof (MyTree))]
interface MyMutableTree {
[Override]
string Name { get; set; }
}
コンストラクターのバインド
この btouch-native
ツールでは、クラスに 4 つのコンストラクターが自動的に生成され、所定のクラス Foo
に対して次のものが生成されます。
Foo ()
: 既定のコンストラクター (Objective-C の "init" コンストラクターにマップされます)Foo (NSCoder)
: NIB ファイルの逆シリアル化中に使用されるコンストラクター (Objective-C の "initWithCoder:" コンストラクターにマップされます)。Foo (IntPtr handle)
: ハンドルベースの作成のコンストラクター。これは、ランタイムが非マネージド オブジェクトからマネージド オブジェクトを公開する必要があるときに、ランタイムによって呼び出されます。Foo (NSEmptyFlag)
: これは、二重初期化を防ぐために派生クラスによって使用されます。
ユーザーが定義するコンストラクターについて、インターフェイス定義内で次のシグネチャを使用して宣言する必要があります。これらは IntPtr
値を返す必要があり、メソッドの名前は Constructor である必要があります。 たとえば、initWithFrame:
コンストラクターをバインドするには、次のようにします。
[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);
プロトコルのバインド
API 設計ドキュメントで説明されているように、モデルとプロトコルに関するセクションでは、Xamarin.iOS によって、Objective-C プロトコルが xxx でフラグ設定されたクラスにマップされます。[Model]
属性。 これは通常、Objective-C デリゲート クラスを実装するときに使用されます。
通常のバインド クラスとデリゲート クラスの大きな違いは、デリゲート クラスには 1 つ以上の省略可能なメソッドがある可能性があるということです。
たとえば、UIKit
クラス UIAccelerometerDelegate
がどのように Xamarin.iOS にバインドされるかを考えてみます。
[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
[Export ("accelerometer:didAccelerate:")]
void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}
これはUIAccelerometerDelegate
の定義に対する省略可能なメソッドであるため、他に何もする必要はありません。 ただし、プロトコルに必要な方法がある場合は、[Abstract]
属性をメソッドに追加します。 こうすると、実装のユーザーはメソッドの本体を実際に指定する必要があります。
一般に、プロトコルはメッセージに応答するクラスで使用されます。 これは通常、Objective-C で、プロトコル内のメソッドに応答するオブジェクトのインスタンスを "デリゲート" プロパティに割り当てることで行われます。
Xamarin.iOS の規則は、デリゲートに NSObject
の任意のインスタンスを割り当てることができる Objective-C 疎結合スタイルと、厳密に型指定されたバージョンの公開の両方をサポートすることです。 このため通常は、厳密に型指定された Delegate
プロパティおよび、緩やかに型指定された WeakDelegate
の両方を指定します。 通常、緩やかに型指定されたバージョンを [Export]
でバインドし、[Wrap]
属性を使用して厳密に型指定されたバージョンを指定します。
これは、UIAccelerometer
クラスをバインドする方法を示しています。
[BaseType (typeof (NSObject))]
interface UIAccelerometer {
[Static] [Export ("sharedAccelerometer")]
UIAccelerometer SharedAccelerometer { get; }
[Export ("updateInterval")]
double UpdateInterval { get; set; }
[Wrap ("WeakDelegate")]
UIAccelerometerDelegate Delegate { get; set; }
[Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
NSObject WeakDelegate { get; set; }
}
MonoTouch 7.0 の新機能
MonoTouch 7.0 以降では、新しく改良されたプロトコル バインド機能が組み込まれています。 この新しいサポートにより、所定のクラスで 1 つ以上のプロトコルを採用するために Objective-C イディオムを使用する方法が簡素化されています。
Objective-C でのすべてのプロトコル定義 MyProtocol
に対して、プロトコルが必要とするすべてのメソッドを一覧表示する IMyProtocol
インターフェイスおよび、すべての省略可能なメソッドを提供する拡張クラスが追加されました。 上記と Xamarin Studio エディターの新しいサポートを組み合わせることで、開発者は以前の抽象モデル クラスの個別のサブクラスを使用しなくても、プロトコル メソッドを実装できます。
[Protocol]
属性を含む定義では、実際には、プロトコルの使用方法を大幅に改善する 3 つのサポート クラスが生成されます。
// Full method implementation, contains all methods
class MyProtocol : IMyProtocol {
public void Say (string msg);
public void Listen (string msg);
}
// Interface that contains only the required methods
interface IMyProtocol: INativeObject, IDisposable {
[Export ("say:")]
void Say (string msg);
}
// Extension methods
static class IMyProtocol_Extensions {
public static void Optional (this IMyProtocol this, string msg);
}
}
クラスの実装では、個々のメソッドをオーバーライドして完全なタイプ セーフを取得できる完全な抽象クラスが提供されます。 ただし、C# では複数の継承がサポートされていないため、別の基底クラスが必要となるシナリオがあります。ただし、インターフェイスを実装すべきシナリオもあります。
生成された インターフェイス定義 が使用されます。 これは、プロトコルから必要なすべてのメソッドを持つインターフェイスです。 これにより、プロトコルを実装する開発者は、そのインターフェイスを実装するだけ済みます。 ランタイムは、プロトコルを採用するように型を自動的に登録します。
インターフェイスは必要なメソッドのみを一覧表示し、省略可能なメソッドが公開されることに注意してください。 つまり、プロトコルを採用するクラスは、必要なメソッドの完全な型チェックを取得しますが、省略可能なプロトコル メソッドについては、弱い型指定 (手動で、[Export]
属性を使用してシグネチャを照合する) に頼る必要があります。
プロトコルを使用する API を使用しやすくするために、バインディング ツールでは、オプションのすべてのメソッドを公開する拡張メソッド クラスも生成されます。 つまり、API を使用している限り、プロトコルはすべてのメソッドを持つものとして扱えることを意味します。
API でプロトコル定義を使用する場合は、API 定義にスケルトンの空のインターフェイスを記述する必要があります。 API で MyProtocol を使用する場合は、次の操作を行う必要があります。
[BaseType (typeof (NSObject))]
[Model, Protocol]
interface MyProtocol {
// Use [Abstract] when the method is defined in the @required section
// of the protocol definition in Objective-C
[Abstract]
[Export ("say:")]
void Say (string msg);
[Export ("listen")]
void Listen ();
}
interface IMyProtocol {}
[BaseType (typeof(NSObject))]
interface MyTool {
[Export ("getProtocol")]
IMyProtocol GetProtocol ();
}
バインド時に IMyProtocol
が存在しないため、上記が必要です。これが、空のインターフェイスを指定する必要がある理由です。
プロトコルで生成されたインターフェイスの採用
プロトコル用に生成されたインターフェイスのいずれかを実装する場合は常に、次のようになります。
class MyDelegate : NSObject, IUITableViewDelegate {
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
必要なインターフェイス メソッドの実装は、適切な名前でエクスポートされるため、次のようになります。
class MyDelegate : NSObject, IUITableViewDelegate {
[Export ("getRowHeight:")]
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
これは、必要なすべてのプロトコル メンバーに対して機能しますが、省略可能なセレクターに注意する特別なケースがあります。 省略可能なプロトコル メンバーは、基底クラスを使用する場合と同じように扱われます。
public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
ただし、プロトコル インターフェイスを使用する場合は、[Export] を追加する必要があります。 オーバーライドから開始して追加すると、IDE のオートコンプリートによって追加されます。
public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
実行時の 2 つの動作には若干の違いがあります。
- 基底クラス (例の NSUrlSessionDownloadDelegate) のユーザーは、必要なすべてのセレクターと省略可能なセレクターを提供し、適切な既定値を返します。
- インターフェイスのユーザー (例の INSUrlSessionDownloadDelegate) は、指定された正確なセレクターにのみ応答します。
ここでは、いくつかのまれなクラスの動作が異なる場合があります。 ただし、ほとんどの場合、どちらでも安全に使用できます。
クラス拡張のバインド
Objective-C では、C# の拡張メソッドと同様に、新しいメソッドを使用してクラスを拡張できます。 これらのメソッドのいずれかが存在する場合は、[BaseType]
属性を使用して、Objective-C メッセージの受信者としてメソッドにフラグを設定します。
たとえば、Xamarin.iOS では、次のように、UIKit
を NSStringDrawingExtensions
のメソッドとしてインポートしたとき、NSString
で定義された拡張メソッドをバインドしました。
[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
[Export ("drawAtPoint:withFont:")]
CGSize DrawString (CGPoint point, UIFont font);
}
Objective-C 引数リストのバインド
Objective-C は可変引数をサポートします。 次に例を示します。
- (void) appendWorkers:(XWorker *) firstWorker, ...
NS_REQUIRES_NIL_TERMINATION ;
C# からこのメソッドを呼び出すには、次のようなシグネチャを作成します。
[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)
これにより、メソッドが内部として宣言され、上記の API がユーザーから非表示になりますが、ライブラリには公開されます。 その後、次のようなメソッドを記述できます。
public void AppendWorkers(params Worker[] workers)
{
if (workers is null)
throw new ArgumentNullException ("workers");
var pNativeArr = Marshal.AllocHGlobal(workers.Length * IntPtr.Size);
for (int i = 1; i < workers.Length; ++i)
Marshal.WriteIntPtr (pNativeArr, (i - 1) * IntPtr.Size, workers[i].Handle);
// Null termination
Marshal.WriteIntPtr (pNativeArr, (workers.Length - 1) * IntPtr.Size, IntPtr.Zero);
// the signature for this method has gone from (IntPtr, IntPtr) to (Worker, IntPtr)
WorkerManager.AppendWorkers(workers[0], pNativeArr);
Marshal.FreeHGlobal(pNativeArr);
}
フィールドのバインド
ライブラリで宣言されたパブリック フィールドにアクセスする必要がある場合があります。
通常、これらのフィールドには、参照する必要がある文字列または整数の値が含まれます。 これらは、一般的に、特定の通知を表す文字列や、ディクショナリ内のキーとして使用されます。
フィールドをバインドするには、インターフェイス定義ファイルにプロパティを追加し、そのプロパティを [Field]
属性で装飾します。 この属性は、検索するシンボルの C 名という 1 つのパラメーターを受け取ります。 次に例を示します。
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
NSObject
から派生しない静的クラスで複数のフィールドをラップするには、次のように、クラスに [Static]
属性を使用します。
[Static]
interface LonelyClass {
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
}
上記では、NSObject
から派生していない LonelyClass
が生成され、NSString
として公開される NSSomeEventNotification
NSString
へのバインドが含まれます。
[Field]
属性は、次のデータ型に適用できます。
NSString
参照 (読み取り専用プロパティのみ)NSArray
参照 (読み取り専用プロパティのみ)- 32 ビット 整数 (
System.Int32
) - 64 ビット 整数 (
System.Int64
) - 32 ビット浮動小数点 (
System.Single
) - 64 ビット浮動小数点 (
System.Double
) System.Drawing.SizeF
CGSize
ネイティブ フィールド名に加えて、ライブラリ名を渡すことで、フィールドが配置されているライブラリ名を指定できます。
[Static]
interface LonelyClass {
[Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
NSString SomeSharedLibrarySymbol { get; }
}
静的にリンクする場合は、バインドするライブラリがないため、__Internal
名を使用する必要があります。
[Static]
interface LonelyClass {
[Field ("MyFieldFromALibrary", "__Internal")]
NSString MyFieldFromALibrary { get; }
}
列挙型のバインド
enum
をバインド ファイルに直接追加すると、別のソース ファイル (バインドと最終的なプロジェクトの両方でコンパイルする必要がある) を使用せずに、API 定義内でそれらを簡単に使用できるようになります。
例:
[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}
interface MyType {
[Export ("initWithEnum:")]
IntPtr Constructor (MyEnum value);
}
独自の列挙型を作成し、NSString
定数を置き換えることもできます。 この場合、ジェネレーターは列挙型の値と NSString 定数を変換するメソッドを自動的に作成します。
例:
enum NSRunLoopMode {
[DefaultEnumValue]
[Field ("NSDefaultRunLoopMode")]
Default,
[Field ("NSRunLoopCommonModes")]
Common,
[Field (null)]
Other = 1000
}
interface MyType {
[Export ("performForMode:")]
void Perform (NSString mode);
[Wrap ("Perform (mode.GetConstant ())")]
void Perform (NSRunLoopMode mode);
}
上記の例では、[Internal]
属性を使用して void Perform (NSString mode);
装飾を装飾することができます。 これにより、バインドのコンシューマーには定数ベースの API が非表示になります。
しかし、より優れた API 代替手段では [Wrap]
属性を使用するので、この場合は型のサブクラス化が制限されます。 生成されたメソッドは virtual
ではないため、オーバーライドできません。これは、適切な選択である場合もそうでない場合もあります。
別の方法として、本来の NSString
ベースである定義を [Protected]
とマークすることもできます。 これにより、サブクラス化は必要に応じて機能し、ラップされたバージョンは引き続き動作し、オーバーライドされたメソッドを呼び出すようになります。
NSValue
、NSNumber
、NSString
をより適切な型にバインドする
この [BindAs]
属性により、より正確な C# 型へ NSNumber
、NSValue
、NSString
(列挙型) をバインドできます。 この属性を使用すると、ネイティブ API よりも優れた、より正確な .NET API を作成できます。
メソッド (戻り値)、パラメーター、プロパティを [BindAs]
を使用して装飾できます。
唯一の制限は、メンバーは [Protocol]
または [Model]
インターフェイス内にあってはならないことです。
次に例を示します。
[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);
次のように出力されます。
[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }
内部的には、bool?
<->NSNumber
変換と CGRect
<->NSValue
変換を行います。
[BindAs]
では、NSNumber
NSValue
および NSString
(列挙型) の 配列もサポートされています。
次に例を示します。
[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }
次のように出力されます。
[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }
CAScroll
は、NSString
に基づく列挙型です。適切な NSString
値をフェッチし、型変換を処理します。
サポートされている変換の種類については、[BindAs]
ドキュメントを参照してください。
通知のバインド
通知は、アプリケーションのある部分から別の部分にメッセージをブロードキャストするメカニズムとして使用される、NSNotificationCenter.DefaultCenter
にポストされるメッセージです。 開発者は通常、NSNotificationCenter の AddObserver メソッドを使用して通知にサブスクライブします。 アプリケーションが通知センターにメッセージをポストすると、通常、NSNotification.UserInfo 辞書に格納されているペイロードが含まれます。 この辞書は弱く型指定されており、また、情報を取得する際、ユーザーは通常、辞書で使用できるキーと辞書に格納できる値の型をドキュメントで読む必要があるため、エラーが発生しやすくなります。 キーの存在は、ブール値としても使用される場合があります。
Xamarin.iOS バインド ジェネレーターは、開発者が通知をバインドするためのサポートを提供します。 これを行うには、プロパティに [Notification]
要素を設定します (その要素は [Field]
プロパティでもタグ付けされており、パブリックでもプライベートでもかまいません)。
この属性は、ペイロードを含まない通知の引数なしで使用できます。または、API 定義内の別のインターフェイスを参照する System.Type
を指定することもできます。通常、これらには名前の末尾に "EventArgs" が付いています。 ジェネレーターは、インターフェイスを EventArgs
サブクラス化するクラスに変換し、そこに一覧表示されているすべてのプロパティを含めます。 [Export]
属性をEventArgs クラスで使用して、値をフェッチする Objective-C 辞書を検索するために使用されるキーの名前をリスト化する必要があります。
次に例を示します。
interface MyClass {
[Notification]
[Field ("MyClassDidStartNotification")]
NSString DidStartNotification { get; }
}
上記のコードでは、次のメソッドを使用して入れ子になったクラス MyClass.Notifications
が生成されます。
public class MyClass {
[..]
public Notifications {
public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
}
}
その後、コードのユーザーは、次のようなコードを使用して、NSDefaultCenter に投稿された通知を簡単にサブスクライブできます。
var token = MyClass.Notifications.ObserverDidStart ((notification) => {
Console.WriteLine ("Observed the 'DidStart' event!");
});
次のように、ObserveDidStart
の戻り値を使用して、通知の受信を簡単に停止できます。
token.Dispose ();
または、NSNotification.DefaultCenter.RemoveObserver を呼び出して、トークンを渡すことができます。 通知にパラメーターが含まれている場合は、次のようにヘルパー EventArgs
インターフェイスを指定する必要があります。
interface MyClass {
[Notification (typeof (MyScreenChangedEventArgs)]
[Field ("MyClassScreenChangedNotification")]
NSString ScreenChangedNotification { get; }
}
// The helper EventArgs declaration
interface MyScreenChangedEventArgs {
[Export ("ScreenXKey")]
nint ScreenX { get; set; }
[Export ("ScreenYKey")]
nint ScreenY { get; set; }
[Export ("DidGoOffKey")]
[ProbePresence]
bool DidGoOff { get; }
}
上記では、NSNotification.UserInfo 辞書からデータをフェッチしする、ScreenX
および ScreenY
プロパティを持つ MyScreenChangedEventArgs
クラスが生成されます。それぞれ、"ScreenXKey" and "ScreenYKey" を使用し、適切な変換を行います。 [ProbePresence]
属性は、値を抽出するのでなく、UserInfo
でキーが設定されているかどうかをジェネレーターがプローブするために使用されます。 これは、キーの存在が値である場合に使用されます (通常はブール値の場合)。
これにより、次のようなコードを記述できます。
var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});
カテゴリのバインド
カテゴリは、クラスで使用できる一連のメソッドとプロパティを拡張するために使用される Objective-C メカニズムです。 実際には、特定のフレームワークがリンクされている場合 (たとえば UIKit
) に基底クラス (たとえば NSObject
) の機能を拡張するか、それらのメソッドを使用できるようにするかのいずれかに使用されますが、新しいフレームワークがリンクされている場合にのみです。 また、クラス内の特徴を機能別に整理するために使用される場合もあります。 これらは、C# 拡張メソッドに似ています。Objective-C ではカテゴリは次のようになります。
@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end
ライブラリで見つかった場合、上記の例では、UIView
のインスタンスをメソッド makeBackgroundRed
で拡張します。
それらをバインドするには、インターフェイス定義で [Category]
属性を使用できます。 - [Category]
属性を使用する際、[BaseType]
属性の意味は、拡張する基底クラスを指定するために使用するものから、拡張する型へと変化します。
UIView
拡張機能がバインドされ、C# 拡張メソッドに変換される仕組みを次に示します。
[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
[Export ("makeBackgroundRed")]
void MakeBackgroundRed ();
}
上記では、MakeBackgroundRed
拡張メソッドを含むクラス MyUIViewExtension
が作成されます。 つまり、任意の UIView
サブクラスで "MakeBackgroundRed" を呼び出し、Objective-C と同じ機能を使用できるようになります。 その他の場合、カテゴリはシステム クラスを拡張するのではなく、装飾のみの目的で、機能を整理するために使用されます。 例:
@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end
@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end
- この定義の装飾スタイルにも [Category]
属性を使用できますが、それらをすべてクラス定義に追加することもできます。 どちらの場合も同じ結果が得られます。
[BaseType (typeof (NSObject))]
interface SocialNetworking {
}
[Category]
[BaseType (typeof (SocialNetworking))]
interface Twitter {
[Export ("postToTwitter:")]
void PostToTwitter (Message message);
}
[Category]
[BaseType (typeof (SocialNetworking))]
interface Facebook {
[Export ("postToFacebook:andPicture:")]
void PostToFacebook (Message message, UIImage picture);
}
これらの場合、カテゴリをマージする方が短くなります。
[BaseType (typeof (NSObject))]
interface SocialNetworking {
[Export ("postToTwitter:")]
void PostToTwitter (Message message);
[Export ("postToFacebook:andPicture:")]
void PostToFacebook (Message message, UIImage picture);
}
ブロックのバインド
ブロックは、C# 匿名メソッドと同等の機能を Objective-C で使用するために、Apple によって導入された新しいコンストラクトです。 たとえば、NSSet
クラスは次のメソッドを公開するようになりました。
- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block
上記の記述では、block
という単一の引数を受け取る、enumerateObjectsUsingBlock:
というメソッドを宣言します。 このブロックは、現在の環境のキャプチャ ("this" ポインター、ローカル変数とパラメーターへのアクセス) をサポートするという点で、C# 匿名メソッドに似ています。 上記の NSSet
でのメソッドは、NSObject
(id obj
の部分) およびブール値 (BOOL *stop
) 部分 へのポインターという、2 つのパラメーターを持つブロックを呼び出します。
この種の API を btouch にバインドするには、まずブロック型シグネチャを C# デリゲートとして宣言し、次のように API エントリ ポイントから参照する必要があります。
// This declares the callback signature for the block:
delegate void NSSetEnumerator (NSObject obj, ref bool stop)
// Later, inside your definition, do this:
[Export ("enumerateObjectUsingBlock:")]
void Enumerate (NSSetEnumerator enum)
これで、コードは C# から関数を呼び出すことができます。
var myset = new NSMutableSet ();
myset.Add (new NSString ("Foo"));
s.Enumerate (delegate (NSObject obj, ref bool stop){
Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});
必要に応じてラムダを使用することもできます。
var myset = new NSMutableSet ();
mySet.Add (new NSString ("Foo"));
s.Enumerate ((obj, stop) => {
Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});
非同期メソッド
バインディング ジェネレーターは、特定のクラスのメソッドを非同期にフレンドリーなメソッド (Task または Task<T> を返すメソッド) に変換できます。
を使用してvoid を返し、メソッドの [Async]
属性。最後の引数はコールバックです。 これをメソッドに適用すると、バインディング ジェネレーターによって、サフィックス Async
を持つそのメソッドのバージョンが生成されます。 コールバックがパラメーターを受け取らない場合、戻り値は Task
になります。コールバックがパラメーターを受け取る場合、結果は Task<T>
になります。 コールバックが複数のパラメーターを受け取る場合、すべてのプロパティを保持する生成された型の目的の名前を指定するように、ResultType
または ResultTypeName
を設定する必要があります。
例:
[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);
上記のコードでは、LoadFile メソッドおよび、次を生成します。
[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);
弱い NSDictionary パラメーターの強い型を公開する
Objective-C API の多くの場所では、パラメーターは特定のキーと値を持つ弱く型指定された NSDictionary
API として渡されますが、エラーが発生しやすく (無効なキーや無効な値を渡しても警告が発生しない場合があります。)、有効なキー名前と値を参照するためにドキュメントに複数回アクセスする必要があるため、ストレスの要因になります。
厳密に型指定されたバージョンの API を指定し、バックグラウンドでさまざまな基になるキーと値をマップする、厳密に型指定されたバージョンを指定することで、これを解決できます。
たとえば、Objective-C API が NSDictionary
を受け入れ、0.0 から 1.0 のボリューム値を持つ NSNumber
および、文字列 XyzCaptionKey
を受け取るキー XyzVolumeKey
を受け取るよう文書化されている場合、ユーザーには次のような適切な API を提供するとよいでしょう。
public class XyzOptions {
public nfloat? Volume { get; set; }
public string Caption { get; set; }
}
Volume
プロパティはnull 許容 float として定義されています。Objective-C の規則では、これらの辞書に値を設定する必要がないため、シナリオによっては値が設定されない可能性があります。
これを行うには、いくつかの操作を行う必要があります。
- DictionaryContainer をサブクラス 化し、各プロパティにさまざまなゲッターとセッターを提供する、厳密に型指定されたクラスを作成します。
- 厳密に型指定された新しいバージョンを取得するために、
NSDictionary
を受け取るメソッドのオーバーロードを宣言します。
厳密に型指定されたクラスの作成は、手動で行うか、それを行うためにジェネレーターを使用することができます。 まず、この方法を手動で調べて、何が起こっているかを理解します。次に自動アプローチについて説明します。
そのためのサポート ファイルを作成する必要があります。コントラクト API には含まれません。 XyzOptions クラスを作成するには、次のように記述する必要があります。
public class XyzOptions : DictionaryContainer {
# if !COREBUILD
public XyzOptions () : base (new NSMutableDictionary ()) {}
public XyzOptions (NSDictionary dictionary) : base (dictionary){}
public nfloat? Volume {
get { return GetFloatValue (XyzOptionsKeys.VolumeKey); }
set { SetNumberValue (XyzOptionsKeys.VolumeKey, value); }
}
public string Caption {
get { return GetStringValue (XyzOptionsKeys.CaptionKey); }
set { SetStringValue (XyzOptionsKeys.CaptionKey, value); }
}
# endif
}
その後、低レベル API に加え、高レベル API を示すラッパー メソッドを提供する必要があります。
[BaseType (typeof (NSObject))]
interface XyzPanel {
[Export ("playback:withOptions:")]
void Playback (string fileName, [NullAllowed] NSDictionary options);
[Wrap ("Playback (fileName, options?.Dictionary")]
void Playback (string fileName, XyzOptions options);
}
API を上書きする必要がない場合は、[Internal] 属性を使用して NSDictionary ベースの API を安全に非表示にすることができます。[Internal]
属性。
ご覧のとおり、[Wrap]
属性を使用して新しい API エントリ ポイントを表示し、厳密に型指定された XyzOptions
クラスを使用してそれを表示します。 ラッパー メソッドでは、null を渡すこともできます。
現時点で、私たちが言及しなかったことの 1 つに、XyzOptionsKeys
値がどこから来たかということがあります。 通常は次のように、API が XyzOptionsKeys
などの静的クラスに表示するキーをグループ化します。
[Static]
class XyzOptionKeys {
[Field ("kXyzVolumeKey")]
NSString VolumeKey { get; }
[Field ("kXyzCaptionKey")]
NSString CaptionKey { get; }
}
これらの厳密に型指定された辞書を作成するための自動サポートを見てみましょう。 これにより、多くの定型句が回避され、外部ファイルを使用せず、API コントラクトで直接辞書を定義できます。
厳密に型指定された辞書を作成するには、API にインターフェイスを導入し、StrongDictionary 属性で 装飾します。 こうすることで、ジェネレーターに対し、DictionaryContainer
から派生し、それに対して厳密な型指定アクセサーを指定するインターフェイスと同じ名前のクラスを作成するように指示します。
[StrongDictionary]
属性は、辞書 キーを含む静的クラスの名前である 1 つのパラメーターを受け取ります。 その後、インターフェイスの各プロパティは、厳密に型指定されたアクセサーになります。 既定では、コードはプロパティの名前と静的クラスのサフィックス "Key" を使用してアクセサーを作成します。
つまり、厳密に型指定されたアクセサーを作成する際に、外部ファイルが不要になり、すべてのプロパティに対してゲッターとセッターを手動で作成する必要も、キーを手動で参照する必要もなくなります。
バインド全体は次のようになります。
[Static]
class XyzOptionKeys {
[Field ("kXyzVolumeKey")]
NSString VolumeKey { get; }
[Field ("kXyzCaptionKey")]
NSString CaptionKey { get; }
}
[StrongDictionary ("XyzOptionKeys")]
interface XyzOptions {
nfloat Volume { get; set; }
string Caption { get; set; }
}
[BaseType (typeof (NSObject))]
interface XyzPanel {
[Export ("playback:withOptions:")]
void Playback (string fileName, [NullAllowed] NSDictionary options);
[Wrap ("Playback (fileName, options?.Dictionary")]
void Playback (string fileName, XyzOptions options);
}
XyzOption
メンバー内で別のフィールド (サフィックス Key
を持つプロパティの名前ではありません) を参照する必要がある場合は、プロパティを 使用する名前を持つ [Export]
属性で装飾します。
型のマッピング
このセクションでは、Objective-C 型を C# 型にマップする仕組みについて説明します。
単純型
次の表は、Objective-C および CocoaTouch ワールドから Xamarin.iOS ワールドに型をマップする方法を示しています。
Objective-C 型名 | Xamarin.iOS Unified API の種類 |
---|---|
BOOL 、GLboolean |
bool |
NSInteger |
nint |
NSUInteger |
nuint |
CFTimeInterval / NSTimeInterval |
double |
NSString (NSStringのバインドの詳細) |
string |
char * |
string ([PlainString] も参照してください) |
CGRect |
CGRect |
CGPoint |
CGPoint |
CGSize |
CGSize |
CGFloat 、GLfloat |
nfloat |
CoreFoundation 型 (CF* ) |
CoreFoundation.CF* |
GLint |
nint |
GLfloat |
nfloat |
基盤の型 (NS* ) |
Foundation.NS* |
id |
Foundation .NSObject |
NSGlyph |
nint |
NSSize |
CGSize |
NSTextAlignment |
UITextAlignment |
SEL |
ObjCRuntime.Selector |
dispatch_queue_t |
CoreFoundation.DispatchQueue |
CFTimeInterval |
double |
CFIndex |
nint |
NSGlyph |
nuint |
配列
Xamarin.iOS ランタイムは、C# 配列の NSArrays
への変換とその逆変換を自動的に処理します。たとえば、UIViews
の NSArray
を返す架空の Objective-C メソッドは次のようになります。
// Get the peer views - untyped
- (NSArray *)getPeerViews ();
// Set the views for this container
- (void) setViews:(NSArray *) views
バインドは次のようになります。
[Export ("getPeerViews")]
UIView [] GetPeerViews ();
[Export ("setViews:")]
void SetViews (UIView [] views);
ここでは、厳密に型指定された C# 配列を使用しています。これにより、IDE はユーザーによる推測を必要とせず、実際の型で適切なコード補完を行ったり、ドキュメントを検索して、配列に含まれる実際の型を判断したりすることができます。
配列に含まれる実際の最も派生した型を追跡できない場合は、NSObject []
を戻り値として使用できます。
セレクター
Objective-C API では、セレクターは、特殊な型 SEL
として表示されます。 セレクターをバインドするには、型を ObjCRuntime.Selector
にマップします。 通常、セレクターは、ターゲット オブジェクトであるオブジェクトおよび、ターゲット オブジェクトで呼び出すセレクターとして API で公開されます。 基本的に、これら両方を持つことで C# デリゲートに対応します。呼び出すメソッドと、メソッドを呼び出すオブジェクトがカプセル化されます。
バインドは次のようになります。
interface Button {
[Export ("setTarget:selector:")]
void SetTarget (NSObject target, Selector sel);
}
これが、メソッドが一般的にアプリケーションで使用される仕組みです。
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
b.SetTarget (this, new Selector ("print"));
}
[Export ("print")]
void ThePrintMethod ()
{
// This does the printing
}
}
C# 開発者がバインディングを適切に行えるようにするには、通常、NSAction
パラメーターを受け取るメソッドを提供します。これにより、Target+Selector
ではなく、C# デリゲートとラムダを使用できるようになります。 これを行うには、通常、SetTarget
メソッドを非表示にします。[Internal]
属性でフラグを付けて非表示にすると、次のように新しいヘルパー メソッドが公開されます。
// API.cs
interface Button {
[Export ("setTarget:selector:"), Internal]
void SetTarget (NSObject target, Selector sel);
}
// Extensions.cs
public partial class Button {
public void SetTarget (NSAction callback)
{
SetTarget (new NSActionDispatcher (callback), NSActionDispatcher.Selector);
}
}
これで、ユーザー コードを次のように記述できます。
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
// First Style
b.SetTarget (ThePrintMethod);
// Lambda style
b.SetTarget (() => { /* print here */ });
}
void ThePrintMethod ()
{
// This does the printing
}
}
文字列
NSString
を受け取るメソッドをバインドする場合は、戻り値の型とパラメーターの両方で、C# 文字列型に置き換えることができます。
文字列がトークンとして使用される場合のみ、NSString
を直接使用する必要があります。 文字列および NSString
の詳細については、「NSString での API 設計」ドキュメントを参照してください。
まれに、API が char *
文字列 (Objective-C) ではなく C のような文字列 (NSString *
) を公開することがあります。 このような場合は、そのパラメーターを [PlainString]
属性。
out/ref パラメーターで注釈します。
一部の API は、パラメーターで値を返すか、参照によってパラメーターを渡します。
通常、シグネチャは次のようになります。
- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref
最初の例では、エラー コードを返す一般的な Objective-C イディオムを示し、 NSError
ポインターへのポインターが渡され、返されるときに値が設定されます。 2 番目のメソッドは、Objective-C メソッドがオブジェクトを取得してその内容を変更する方法を示しています。 これは純粋な出力値ではなく、参照渡しになります。
バインドは次のようになります。
[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);
メモリ管理属性
[Export]
属性を使用し、呼び出されたメソッドによって保持されるデータを渡す場合は、引数セマンティクスを 2 番目のパラメーターとして渡すことで指定できます。次に例を示します。
[Export ("method", ArgumentSemantic.Retain)]
上記では、値が "Retain" セマンティクスを持つものとしてフラグを立てます。 使用できるセマンティクスは次のとおりです。
- Assign
- コピー
- 保持する
スタイルのガイドライン
[Internal] の使用
を使用して[Internal]
属性は、メソッドをパブリック API から非表示にします。 これは、公開されている API のレベルが低すぎて、このメソッドに基づいて別のファイルに高レベルの実装を提供したい場合に行うことができます。
また、バインド ジェネレーターで制限が発生した場合にも使用できます。たとえば、一部の高度なシナリオでは、バインドされていない型が公開され、独自の方法でバインドしたり、独自の方法でそれらの型をラップしたりする必要があります。
イベント ハンドラーとコールバック
Objective-C クラスは、通常、デリゲート クラス (Objective-C デリゲート) にメッセージを送信して通知または要求情報をブロードキャストします。
このモデルは、Xamarin.iOS で完全にサポートされ、公開されますが、扱いにくい場合があります。 Xamarin.iOS は、これらの状況で使用できるクラスに C# イベント パターンとメソッド コールバック システムを公開します。 これにより、次のようなコードを実行できます。
button.Clicked += delegate {
Console.WriteLine ("I was clicked");
};
バインディング ジェネレーターは、Objective-C パターンを C# パターンにマップするために必要な入力量を減らすことができます。
Xamarin.iOS 1.4 以降では、特定の Objective-C デリゲートのバインドを生成し、デリゲートをホスト型の C# イベントおよびプロパティとして公開するようにジェネレーターに指示することもできます。
このプロセスには 2 つのクラスが関係しています。1 つは、現在イベントを出力するホスト クラスであり、Delegate
または WeakDelegate
に送信します。もう 1 つは、実際のデリゲート クラスです。
次のセットアップを検討してください。
[BaseType (typeof (NSObject))]
interface MyClass {
[Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
NSObject WeakDelegate { get; set; }
[Wrap ("WeakDelegate")][NullAllowed]
MyClassDelegate Delegate { get; set; }
}
[BaseType (typeof (NSObject))]
interface MyClassDelegate {
[Export ("loaded:bytes:")]
void Loaded (MyClass sender, int bytes);
}
クラスをラップするには、次の条件を満たす必要があります。
- ホスト クラスで、
[BaseType]
宣言に対し、
そのデリゲートとして機能する型と、公開した C# 名を追加します。 上記の例では、それぞれtypeof (MyClassDelegate)
とWeakDelegate
です。 - デリゲート クラスでは、2 つ以上のパラメーターを持つ各メソッドで、自動的に生成される EventArgs クラスに使用する型を指定する必要があります。
バインド ジェネレーターは、1 つのイベントの宛先のみをラップするだけでなく、一部の Objective-C クラスで複数のデリゲートにメッセージを出力する可能性があるため、このセットアップをサポートするために配列を指定する必要があります。 ほとんどのセットアップでは必要ありませんが、ジェネレーターはこれらのケースをサポートしています。
結果のコードは次のようになります。
[BaseType (typeof (NSObject),
Delegates=new string [] {"WeakDelegate"},
Events=new Type [] { typeof (MyClassDelegate) })]
interface MyClass {
[Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
NSObject WeakDelegate { get; set; }
[Wrap ("WeakDelegate")][NullAllowed]
MyClassDelegate Delegate { get; set; }
}
[BaseType (typeof (NSObject))]
interface MyClassDelegate {
[Export ("loaded:bytes:"), EventArgs ("MyClassLoaded")]
void Loaded (MyClass sender, int bytes);
}
EventArgs
は、生成する EventArgs
クラスの名前を指定するために使用されます。 シグネチャごとに 1 つを使用する必要があります (この例では、EventArgs
にnint 型の With
プロパティが含まれます)。
上記の定義では、ジェネレーターは生成された MyClass で次のイベントを生成されます。
public MyClassLoadedEventArgs : EventArgs {
public MyClassLoadedEventArgs (nint bytes);
public nint Bytes { get; set; }
}
public event EventHandler<MyClassLoadedEventArgs> Loaded {
add; remove;
}
そのため、次のようなコードを使用可能です。
MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};
コールバックはイベント呼び出しとほぼ同じです。違いは、コールバックは複数のサブスクライバー (たとえば、複数のメソッドが Clicked
イベントまたは DownloadFinished
イベントにフックすることができます) を持つことはできず、1 つのサブスクライバーしか持つことができないということです。
プロセスは類似していますが、唯一の違いは、生成されるクラスの EventArgs
名前を公開するのではなく、EventArgs を使用して結果の C# デリゲート名に名前を付ける点です。
デリゲート クラスのメソッドが値を返す場合、バインディング ジェネレーターはこれをイベントの代わりに親クラスのデリゲート メソッドにマップします。 このような場合は、ユーザーがデリゲートにフックしない場合にメソッドによって返される既定値を指定する必要があります。 これを行うには、[DefaultValue]
または [DefaultValueFromArgument]
属性を使用します。
[DefaultValue]
は戻り値をハードコードしますが、[DefaultValueFromArgument]
は、返される入力引数を指定するために使用されます。
列挙型と基本型
また、btouch インターフェイス定義システムで直接サポートされていない列挙型または基本型を参照することもできます。 これを行うには、列挙型とコア型を別のファイルに配置し、これを btouch に与える追加ファイルの 1 つとして含めます。
依存関係のリンク
アプリケーションに含まれていない API をバインドする場合は、実行可能ファイルがこれらのライブラリに対してリンクされていることを確認する必要があります。
ライブラリをリンクする方法を Xamarin.iOS に通知する必要があります。1 つは、ビルド構成を mtouch
コマンドで呼び出すように変更することです。これには、"-gcc_flags" オプションと、その後にプログラムに必要な追加ライブラリすべてを含む引用符付き文字列を使用して、追加のビルド引数でライブラリとのリンク方法を指定します。次に例を示します。
-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"
上記の例では、libMyLibrary.a
、libSystemLibrary.dylib
、CFNetwork
フレームワーク ライブラリを最終的な実行可能ファイルにリンクします。
または、コントラクト ファイル (AssemblyInfo.cs
など) に埋め込むアセンブリ レベル [LinkWithAttribute]
を利用することができます。
[LinkWithAttribute]
を使用する場合は、バインドを行うときにネイティブ ライブラリを使用できるようにする必要があります。これにより、ネイティブ ライブラリがアプリケーションに埋め込まれます。 次に例を示します。
// Specify only the library name as a constructor argument and specify everything else with properties:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget = LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]
// Or you can specify library name *and* link target as constructor arguments:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]
-force_load
コマンドが必要な理由は、-ObjC フラグはコードをコンパイルしますが、Xamarin.iOS の実行時に必要なカテゴリをサポートするために必要なメタデータを保持しないということです (リンカー/コンパイラのデッド コード除去によって削除されます)。
支援付きリファレンス
アクション シートやアラート ボックスなどの一時的なオブジェクトの中には、開発者には追跡づらいものもあります。この場合、バインド ジェネレーターが多少役立ちます。
たとえば、メッセージを表示して Done
イベントを生成するクラスがある場合、これは従来、次のように処理されました。
class Demo {
MessageBox box;
void ShowError (string msg)
{
box = new MessageBox (msg);
box.Done += { box = null; ... };
}
}
上記のシナリオでは、開発者はオブジェクトへの参照を自分で保持し、リークするか、ボックスの参照を積極的にクリアすることも自分で行う必要があります。 コードのバインド中、ジェネレーターは参照を追跡し、特別なメソッドが呼び出されたときにそれをクリアすることをサポートしています。上記のコードは次のようになります。
class Demo {
void ShowError (string msg)
{
var box = new MessageBox (msg);
box.Done += { ... };
}
}
変数をインスタンスに保持する必要がなくなり、ローカル変数で動作し、オブジェクトがなくなったときに参照をクリアする必要がなくなっていることに注意してください。
これを利用するには、クラスの [BaseType]
宣言に Events プロパティが設定されている必要があります。また、オブジェクトの処理が完了したときに呼び出されるメソッドの名前に KeepUntilRef
変数が設定されている必要があります。例を示します。
[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
[Export ("show")]
void Show (string message);
}
プロトコルの継承
Xamarin.iOS v3.2 の時点では、[Model]
プロパティでマークされているプロトコルからの継承がサポートされています。 MapKit
など、MKOverlay
プロトコルが MKAnnotation
プロトコルが継承され、NSObject
から継承される複数のクラスで使用される特定の API パターンで役立ちます。
これまでは、すべての実装にプロトコルをコピーする必要がありましたが、これらの場合は、MKShape
クラスが MKOverlay
プロトコルから継承できるようになり、必要なすべてのメソッドが自動的に生成されます。