Objective-C селекторы в Xamarin.iOS
Язык Objective-C основан на селекторах. Селектор — это сообщение, которое можно отправить в объект или класс. Xamarin.iOS сопоставляет селекторы экземпляров с методами экземпляра и селекторами классов со статическими методами.
В отличие от обычных функций C (и таких как функции-члены C++), вы не можете напрямую вызвать селектор с помощью P/Invoke Вместо этого, селекторы отправляются в Objective-C класс или экземпляр с помощью функции objc_msgSend
Функции.
Дополнительные сведения о сообщениях Objective-Cсм. в руководстве apple по работе с объектами .
Пример
Предположим, вы хотите вызвать sizeWithFont:forWidth:lineBreakMode:
селектор в NSString
.
Объявление (из документации Apple) — это:
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
Этот API имеет следующие характеристики:
- Тип возвращаемого значения предназначен
CGSize
для единого API. - Параметр
font
— это UIFont (и тип (косвенно), производный от NSObject, и сопоставляется с System.IntPtr. - Параметр
width
, aCGFloat
, сопоставляется сnfloat
. - Параметр
lineBreakMode
, aUILineBreakMode
, уже был привязан в 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 байт (например, более старый SizeF
, используемый перед переходом на объединенные API), приведенный выше код будет выполняться на симуляторе, но произошел сбой на устройстве. Чтобы вызвать селектор, возвращающий значение меньше 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
свойство для получения IntPtr
экземпляра Objective-C типа.
Существует несколько objc_msgSend
функций:
- Используется
objc_msgSend_stret
для селекторов, возвращающих структуру. В ARM сюда входят все типы возвращаемых данных, которые не являются перечислением или любым из встроенных типов C (char
, ,short
,long
int
,float
).double
В x86 (симуляторе) этот метод необходимо использовать для всех структур размером более 8 байт (CGSize
8 байтов и не используетсяobjc_msgSend_stret
в симуляторе). - Используйте
objc_msgSend_fpret
для селекторов, возвращающих значение с плавающей запятой только в x86. Эта функция не требуется использовать в ARM; вместо этого используйтеobjc_msgSend
. - Основная функция objc_msgSend используется для всех остальных селекторов.
Когда вы решите, какие objc_msgSend
функции необходимо вызвать (симулятор и устройство могут требовать другой метод), можно использовать обычный [DllImport]
метод для объявления функции для последующего вызова.
Набор готовых objc_msgSend
объявлений можно найти в ObjCRuntime.Messaging
.
Различные вызовы на симуляторе и устройстве
Как описано выше, Objective-C имеет три типа objc_msgSend
методов: один для регулярных вызовов, один для вызовов, возвращающих значения с плавающей запятой (только x86), и один для вызовов, возвращающих значения структуры. Последний включает суффикс _stret
в ObjCRuntime.Messaging
.
При вызове метода, возвращающего определенные структуры (правила, описанные ниже), необходимо вызвать метод с возвращаемым значением в качестве первого параметра в качестве 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 используйте objc_msgSend_stret
для любого типа значения, который не является перечислением или любым из базовых типов для перечисления (int
, , byte
, short
long
, ). float
double
При сборке для x86 используйте objc_msgSend_stret
для любого типа значения, который не является перечислением или любым из базовых типов для перечисления (int
, , byte
, short
long
, double
) float
и собственный размер которого превышает 8 байт.
Создание собственных подписей
При необходимости можно использовать следующий gist для создания собственных подписей.