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 , a CGFloat, сопоставляется с nfloat.
  • Параметр lineBreakMode , a 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 байт (например, более старый 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
    );

Вызов селектора

Вызов селектора состоит из трех шагов:

  1. Получите целевой объект селектора.
  2. Получите имя селектора.
  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 отправляет сообщение (селектор) в объект. Это семейство функций принимает по крайней мере два обязательных аргумента: целевой объект селектора (дескриптор экземпляра или класса), сам селектор и все аргументы, необходимые для селектора. Аргументы экземпляра и селектора должны быть System.IntPtr, и все остальные аргументы должны соответствовать типу, который ожидает селектор, например nint для intселектора или System.IntPtr для всех NSObjectпроизводных типов. Используйте NSObject.Handle свойство для получения IntPtr экземпляра Objective-C типа.

Существует несколько objc_msgSend функций:

  • Используется objc_msgSend_stret для селекторов, возвращающих структуру. В ARM сюда входят все типы возвращаемых данных, которые не являются перечислением или любым из встроенных типов C (char, , short, longint, 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, shortlong, ). floatdouble

При сборке для x86 используйте objc_msgSend_stretдля любого типа значения, который не является перечислением или любым из базовых типов для перечисления (int, , byte, shortlong, double) floatи собственный размер которого превышает 8 байт.

Создание собственных подписей

При необходимости можно использовать следующий gist для создания собственных подписей.