Objective-C Xamarin.iOS 中的選取器

語言Objective-C是以選取器為基礎。 選取器是可以傳送至物件或 類別的訊息。 Xamarin.iOS 會將實例選取器對應至實例方法,並將類別選取器對應至靜態方法。

不同於一般 C 函式(和 C++ 成員函式),您無法使用 P/Invoke 直接叫用選取器,選取器會使用 傳送至Objective-C類別或實例objc_msgSend 功能。

如需 中 Objective-C訊息的詳細資訊,請參閱Apple使用 物件 指南。

範例

假設您想要叫用 sizeWithFont:forWidth:lineBreakMode: 上的 NSString選取器。 宣告(來自蘋果的檔案) 是:

- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode

此 API 具有下列特性:

  • 傳回類型適用於 CGSize 整合 API。
  • 參數font是 UIFont (且間接衍生自 NSObject 的類型,且會對應至 System.IntPtr
  • 參數 widthCGFloat,會對應至 nfloat
  • 參數 lineBreakMode ,已在 UILineBreakModeXamarin.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 個字節的結構(例如切換至整合 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
    );

叫用選取器

叫用選取器有三個步驟:

  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,而且所有剩餘的自變數都必須符合選取器所預期的類型,例如 nintintSystem.IntPtr 所有 NSObject衍生型別的 。 使用 NSObject.Handle 要取得 IntPtr 型別 Objective-C 實例的屬性。

有一個 objc_msgSend 以上的函式:

  • 用於 objc_msgSend_stret 傳回結構的選取器。 在 ARM 上,這包括所有不是列舉型別的傳回型別,或任何 C 內建型別(char、、、intshortlongdoublefloat。 在 x86(模擬器)上,此方法必須用於大小大於 8 個字節的所有結構(CGSize 是 8 個字節,不適用於 objc_msgSend_stret 模擬器)。
  • 用於 objc_msgSend_fpret 只傳回 x86 上浮點值的選取器。 此函式不需要用於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);

何時使用 _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、、shortlongdoublefloat

建置 x86 時,請使用 objc_msgSend_stret對於不是列舉型別的任何實值型別,或是列舉型別的任何基底型別,intdoublefloatbyteshortlong其原生大小大於8個字節。

建立您自己的簽章

如有需要,您可以使用下列 gist 來建立您自己的簽章。