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
参数是一个 UIFont(以及从 NSObject 派生的类型(间接)),并映射到 System.IntPtr。width
参数(一个CGFloat
)映射到nfloat
。lineBreakMode
参数(一个UILineBreakMode
)已在 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
);
如果返回值是小于 8 字节的结构(例如切换到 Unified API 之前使用的较旧 SizeF
),则上述代码将在模拟器上运行,但在设备上会崩溃。 若要调用返回小于 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
);
调用选择器
调用选择器需要执行三个步骤:
- 获取选择器目标。
- 获取选择器名称。
- 结合相应的参数调用
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
向对象发送消息(选择器)。 该函数系列至少采用两个必需参数:选择器目标(实例或类句柄)、选择器本身,以及选择器所需的任何参数。 实例和选择器参数必须为 System.IntPtr
,并且所有剩余参数必须与选择器预期的类型匹配,例如,nint
匹配 int
,或者 System.IntPtr
匹配所有 NSObject
派生类型。 使用 NSObject.Handle
属性,用于获取 Objective-C 类型实例的 IntPtr
。
有多个 objc_msgSend
函数:
- 对于返回结构的选择器,请使用
objc_msgSend_stret
。 在 ARM 上,这包括所有非枚举的返回类型或任何 C 内置类型(char
、short
、int
、long
、float
、double
)的返回类型。 在 x86(模拟器)上,此方法需用于大于 8 字节的所有结构(CGSize
是 8 字节,在模拟器中不使用objc_msgSend_stret
)。 - 对于仅在 x86 上返回浮点值的选择器,请使用
objc_msgSend_fpret
。 在 ARM 上不需要使用该函数;请改用objc_msgSend
。 - 主 objc_msgSend 函数用于所有其他选择器。
确定需要调用哪个 objc_msgSend
函数(模拟器和设备可能各自需要不同的方法)后,可以使用普通的 [DllImport]
方法来声明该函数以供稍后调用。
可以在 ObjCRuntime.Messaging
中找到一组预制的 objc_msgSend
声明。
模拟器和设备上的不同调用
如上所述,Objective-C 具有三种 objc_msgSend
方法:一种用于常规调用,一种用于返回浮点值(仅限 x86)的调用,一种用于返回结构值的调用。 后者在 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);
有关在 x86 和 ARM 上何时使用 _stret_
方法的规则有所不同。
如果你希望绑定在模拟器和设备上都能正常工作,请添加如下代码:
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 生成时,请将 objc_msgSend_stret
用于表示非枚举的任何值类型或枚举的任何基类型(int
、byte
、short
、long
、double
、float
)。
为 x86 生成时,请将 objc_msgSend_stret
用于任何非枚举的值类型或枚举的、其原生大小大于 8 字节的任何基类型(int
、byte
、short
、long
、double
、float
)。
创建自己的签名
如果需要,可以使用以下 gist 创建你自己的签名。