Xamarin.iOS の Objective-C セレクター
Objective-C 言語は "セレクター" に基づいています。 セレクターは、オブジェクトまたは "クラス" に送信できるメッセージです。 Xamarin.iOS は、インスタンス セレクターをインスタンス メソッドに、クラス セレクターを静的メソッドにマップします。
通常の C 関数 (および C++ メンバー関数など) とは異なり、代わりに P/Invoke を使用してセレクターを直接呼び出すことはできません。代わりに、セレクターは Objective-C のクラスやインスタンスに対して、objc_msgSend
関数を使用して送信できます。
Objective-C のメッセージの詳細については、Apple の「オブジェクトの操作」ガイドを参照してください。
例
次のことを行いたいと仮定します。NSString
で sizeWithFont:forWidth:lineBreakMode:
セレクターを呼び出す。
宣言は以下のとおりです (Apple のドキュメントから)。
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
この API には以下の特徴があります。
- Unified API での戻り値の型は
CGSize
です。 font
パラメーターは、NSObject から派生した UIFont (および間接的に派生している型) であり、System.IntPtr にマップされます。CGFloat
であるwidth
パラメーターはnfloat
にマップされます。UILineBreakMode
であるlineBreakMode
パラメーターは、Xamarin.iOS で既にUILineBreakMode
列挙型としてバインドされています。
すべてをまとめると、objc_msgSend
宣言は以下のとおりとする必要があります。
CGSize objc_msgSend(
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
これを以下のように宣言します。
[DllImport (Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend")]
static extern CGSize cgsize_objc_msgSend_IntPtr_float_int (
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
このメソッドを呼び出すには、以下のようなコードを使用します。
NSString target = ...
Selector selector = new Selector ("sizeWithFont:forWidth:lineBreakMode:");
UIFont font = ...
nfloat width = ...
UILineBreakMode mode = ...
CGSize size = cgsize_objc_msgSend_IntPtr_float_int(
target.Handle,
selector.Handle,
font == null ? IntPtr.Zero : font.Handle,
width,
mode
);
戻り値が (Unified API への切り替え前に使用されていた古い SizeF
のように) 8 byte 未満のサイズの構造体であった場合、上記のコードはシミュレーター上では実行できてもデバイス上ではクラッシュしていました。 8 ビット未満のサイズの値を返すセレクターを呼び出すには、以下のように objc_msgSend_stret
関数を宣言します。
[DllImport (MonoTouch.Constants.ObjectiveCLibrary, EntryPoint="objc_msgSend_stret")]
static extern void cgsize_objc_msgSend_stret_IntPtr_float_int (
out CGSize retval,
IntPtr target,
IntPtr selector,
IntPtr font,
nfloat width,
UILineBreakMode mode
);
このメソッドを呼び出すには、以下のようなコードを使用します。
NSString target = ...
Selector selector = new Selector ("sizeWithFont:forWidth:lineBreakMode:");
UIFont font = ...
nfloat width = ...
UILineBreakMode mode = ...
CGSize size;
if (Runtime.Arch == Arch.SIMULATOR)
size = cgsize_objc_msgSend_IntPtr_float_int(
target.Handle,
selector.Handle,
font == null ? IntPtr.Zero : font.Handle,
width,
mode
);
else
cgsize_objc_msgSend_stret_IntPtr_float_int(
out size,
target.Handle, selector.Handle,
font == null ? IntPtr.Zero: font.Handle,
width,
mode
);
セレクターの呼び出し
セレクターの呼び出しには、以下の 3 つの手順があります。
- セレクター ターゲットを取得する。
- セレクター名を取得する。
- 適切な引数を使用して
objc_msgSend
を呼び出す。
セレクター ターゲット
セレクター ターゲットは、オブジェクト インスタンスまたは Objective-C クラスです。 ターゲットがインスタンスであり、バインドされた Xamarin.iOS 型に由来する場合は、ObjCRuntime.INativeObject.Handle
プロパティを使用します。
ターゲットがクラスの場合は、ObjCRuntime.Class
を使用してクラス インスタンスへの参照を取得した後、Class.Handle
プロパティを使用します。
セレクター名
セレクター名は、Apple のドキュメントに記載されています。 たとえば、NSString
には sizeWithFont:
および sizeWithFont:forWidth:lineBreakMode:
セレクターが含まれています。 途中のコロンと末尾のコロンはセレクター名の一部であり、省略できません。
セレクター名を取得したら、それに対する ObjCRuntime.Selector
インスタンスを作成できます。
objc_msgSend の呼び出し
objc_msgSend
はメッセージ (セレクター) をオブジェクトに送信します。 この関数ファミリは、セレクター ターゲット (インスタンスまたはクラスのハンドル)、セレクター自体、およびセレクターに必要なすべての引数という少なくとも 2 つの必須引数を受け取ります。 インスタンス引数とセレクター引数は System.IntPtr
で、残りのすべての引数はセレクターが想定する型と一致する必要があります (たとえば、int
に対しては nint
、NSObject
から派生する型に対しては System.IntPtr
)。 を使用するObjective-C 型インスタンスの IntPtr
を取得するための NSObject.Handle
プロパティ。
以下のように objc_msgSend
関数は複数存在します。
- 構造体を返すセレクターには
objc_msgSend_stret
を使用します。 ARM では、これには列挙型でも C の組み込み型 (char
、short
、int
long
、float
、double
) のいずれでもないすべての戻り値の型が含まれます。 x86 (シミュレーター) では、サイズが 8 byte (CGSize
は 8 byte であり、シミュレーターではobjc_msgSend_stret
を使用しません) を超えるすべての構造体に対して、このメソッドを使用する必要があります。 - x86 でのみ浮動小数点値を返すセレクターに
objc_msgSend_fpret
を使用します。 この関数は ARM で使用する必要はありません。代わりにobjc_msgSend
を使用します。 - メインの objc_msgSend 関数は、他のセレクターすべてに対して使用されます。
どの objc_msgSend
関数を呼び出す必要があるかを決定したら (シミュレーターとデバイスでそれぞれ異なるメソッドが必要になる場合があります)、通常の [DllImport]
メソッドを使用して、後で呼び出すための関数を宣言できます。
事前に作成された objc_msgSend
宣言のセットは ObjCRuntime.Messaging
内にあります。
シミュレーターとデバイス上の異なる呼び出し
前述のように、Objective-C には 3 種類の objc_msgSend
メソッドがあります。1 つは通常の呼び出し用、1 つは浮動小数点値を返す呼び出し用 (x86 のみ)、1 つは構造体値を返す呼び出し用です。 最後のものには ObjCRuntime.Messaging
内で _stret
というサフィックスが付いています。
特定の構造体 (以下で説明する規則を参照) を返すメソッドを呼び出す場合、以下のように、戻り値があるメソッドは最初のパラメーターを out
値として呼び出す必要があります。
// The following returns a PointF structure:
PointF ret;
Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, this.Handle, selConvertPointFromWindow.Handle, point, window.Handle);
いつ _stret_
メソッドを使用するべきかの規則は、x86 と ARM で異なります。
バインディングをシミュレーターとデバイスの両方で機能させたい場合は、次のようなコードを追加します。
if (Runtime.Arch == Arch.DEVICE)
{
PointF ret;
Messaging.PointF_objc_msgSend_stret_PointF_IntPtr (out ret, myHandle, selector.Handle);
return ret;
}
else
{
return Messaging.PointF_objc_msgSend_PointF_IntPtr (myHandle, selector.Handle);
}
objc_msgSend_stret メソッドの使用
ARM 用にビルドする場合は、列挙型でもなく、列挙型のための基本データ型 (int
、byte
、short
、long
、double
、float
) のいずれでもない値の型に対してはすべて objc_msgSend_stret
を使用します。
x86 用にビルドする場合は、列挙型でもなく、列挙型のための基本データ型 (int
、byte
、short
、long
、double
、float
) のいずれでもなく、かつネイティブ サイズが 8 byte より大きい値の型に対してはすべて objc_msgSend_stret
を使用します。
独自のシグネチャの作成
必要であれば、こちらの gist を使用して独自のシグネチャを作成できます。