共用方式為


系結 Objective-C 連結庫

使用 Xamarin.iOS 或 Xamarin.Mac 時,您可能會遇到想要取用第三方 Objective-C 連結庫的情況。 在這些情況下,您可以使用 Xamarin 系結專案來建立原生連結庫的 Objective-C C# 系結。 專案會使用我們用來將 iOS 和 Mac API 帶入 C# 的相同工具。

本文件說明如何系結 API,如果您只系結 Objective-C C API,您應該為此 P/Invoke 架構使用標準 .NET 機制。 如需有關如何以靜態方式連結 C 連結庫的詳細數據,請參閱 連結原生連結庫 頁面。

請參閱我們的隨附 系結類型參考指南。 此外,如果您想要深入瞭解幕後發生的情況,請查看我們的系 結概觀 頁面。

您可以針對 iOS 和 Mac 連結庫建置系結。 此頁面描述如何在 iOS 系結上運作,但 Mac 系結非常類似。

iOS 的範例程式代碼

您可以使用 iOS 系 結範例 項目來實驗系結。

開始使用

建立系結最簡單的方式是建立 Xamarin.iOS 系結專案。 您可以從 Visual Studio for Mac 選取專案類型 iOS > 連結庫 > 系結庫來執行此動作:

從 Visual Studio for Mac 選取專案類型、iOS 連結庫系結庫來執行此動作

產生的專案包含您可以編輯的小型範本,其中包含兩個檔案: ApiDefinition.csStructsAndEnums.cs

ApiDefinition.cs是您將定義 API 合約的位置,這是描述基礎 Objective-C API 投影到 C# 的檔案。 此檔案的語法和內容是本文件討論的主要主題,其內容僅限於 C# 介面和 C# 委派宣告。 檔案 StructsAndEnums.cs 是檔案,您會在其中輸入介面和委派所需的任何定義。 這包括程式代碼可能使用的列舉值和結構。

系結 API

若要執行完整的系結,您會想要瞭解 Objective-C API 定義,並熟悉 .NET Framework 設計指導方針。

若要系結您的連結庫,您通常會從 API 定義檔開始。 API 定義檔只是包含 C# 介面的 C# 原始程式檔,這些介面已加上少數屬性的批注,可協助驅動系結。 此檔案定義 C# 與 Objective-C 之間的合約。

例如,這是連結庫的簡單 API 檔案:

using Foundation;

namespace Cocos2D {
  [BaseType (typeof (NSObject))]
  interface Camera {
    [Static, Export ("getZEye")]
    nfloat ZEye { get; }

    [Export ("restore")]
    void Restore ();

    [Export ("locate")]
    void Locate ();

    [Export ("setEyeX:eyeY:eyeZ:")]
    void SetEyeXYZ (nfloat x, nfloat y, nfloat z);

    [Export ("setMode:")]
    void SetMode (CameraMode mode);
  }
}

上述範例會定義名為 Cocos2D.Camera 的類別,其衍生自 NSObject 基底型別(這個型別來自 Foundation.NSObject),並定義靜態屬性 (ZEye),兩個方法不接受自變數,以及採用三個自變數的方法。

以下的 API 定義檔案一節將深入討論 API 檔案的格式和您可以使用的屬性。

若要產生完整的系結,您通常會處理四個元件:

  • API 定義檔 (ApiDefinition.cs 在範本中)。
  • 選擇性:API 定義檔所需的任何列舉、類型、結構(StructsAndEnums.cs 在範本中)。
  • 選擇性:可能會展開產生的系結的額外來源,或提供更適合 C# 的 API(您新增至專案的任何 C# 檔案)。
  • 您要繫結的原生連結庫。

此圖表顯示檔案之間的關聯性:

此圖表顯示檔案之間的關聯性

API 定義檔案只會包含命名空間和介面定義(包含介面可以包含的任何成員),且不應包含類別、列舉、委派或結構。 API 定義檔只是用來產生 API 的合約。

任何您需要的額外程序代碼,例如列舉或支持類別都應該裝載在個別的檔案上,在上述範例中,“CameraMode” 是 CS 檔案中不存在且應該裝載於個別檔案中的列舉值,例如 StructsAndEnums.cs

public enum CameraMode {
    FlyOver, Back, Follow
}

檔案 APIDefinition.cs 會與 類別結合, StructsAndEnum 並用來產生連結庫的核心系結。 您可以依目前方式使用產生的連結庫,但一般而言,您會想要微調產生的連結庫,以新增一些 C# 功能,以利於使用者。 一些範例包括實作 ToString() 方法、提供 C# 索引器、新增對某些原生類型的隱含轉換,或提供某些方法的強型別版本。 這些改善會儲存在額外的 C# 檔案中。 只要將 C# 檔案新增至您的專案,這些檔案就會包含在此建置程式中。

這說明如何在檔案 Extra.cs 中實作程序代碼。 請注意,您將使用部分類別,因為這些類別會增強從 和 StructsAndEnums.cs 核心系結組合ApiDefinition.cs產生的部分類別:

public partial class Camera {
    // Provide a ToString method
    public override string ToString ()
    {
         return String.Format ("ZEye: {0}", ZEye);
    }
}

建置連結庫會產生您的原生系結。

若要完成此系結,您應該將原生連結庫新增至專案。 您可以將原生連結庫新增至您的專案,方法是將原生連結庫從 Finder 拖放至方案總管中的專案,或以滑鼠右鍵按兩下專案,然後選擇 [新增>檔案] 以選取原生連結庫。 依慣例,原生連結庫的開頭是 “lib” 一詞,並以擴展名 “.a” 結尾。 當您這樣做時,Visual Studio for Mac 會新增兩個檔案:.a 檔案和自動填入的 C# 檔案,其中包含原生連結庫包含的資訊:

依慣例,原生連結庫的開頭為 lib,並以擴展名結尾。a

檔案的內容 libMagicChord.linkwith.cs 包含如何使用此連結庫的資訊,並指示 IDE 將此二進位檔封裝到產生的 DLL 檔案中:

using System;
using ObjCRuntime;

[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]

如何使用的完整詳細數據 [LinkWith] 屬性記載於系 結類型參考指南中

現在當您建置專案時,最後會包含 MagicChords.dll 系結和原生連結庫的檔案。 您可以將此項目或產生的 DLL 散發給其他開發人員以供自己使用。

有時候,您可能會發現您需要一些列舉值、委派定義或其他類型。 請勿將那些放在 API 定義檔案中,因為這隻是合約

API 定義檔

API 定義檔是由一些介面所組成。 API 定義中的介面將會變成類別宣告,而且它們必須以 [BaseType] 屬性裝飾,才能指定 類別的基類。

您可能想知道為什麼我們沒有使用類別,而不是合約定義的介面。 我們挑選了介面,因為它允許我們撰寫方法的合約,而不需要在 API 定義檔中提供方法主體,或必須提供必須擲回例外狀況或傳回有意義值的主體。

但是,因為我們使用 介面作為基本架構來產生類別,因此我們必須使用屬性裝飾合約的各個部分,以驅動系結。

系結方法

您可以做的最簡單的系結是系結方法。 只要使用 C# 命名慣例在 介面中宣告方法,並使用 裝飾方法 [Export] 屬性。 屬性 [Export] 是連結 C# 名稱與 Objective-C Xamarin.iOS 執行時間中名稱的專案。 的參數 [Export] attribute 是選取器的名稱 Objective-C 。 一些範例:

// A method, that takes no arguments
[Export ("refresh")]
void Refresh ();

// A method that takes two arguments and return the result
[Export ("add:and:")]
nint Add (nint a, nint b);

// A method that takes a string
[Export ("draw:atColumn:andRow:")]
void Draw (string text, nint column, nint row);

上述範例示範如何系結實例方法。 若要系結靜態方法,您必須使用 [Static] 屬性,如下所示:

// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();

這是必要的,因為合約是介面的一部分,而且介面沒有靜態與實例宣告的概念,因此必須再次訴諸屬性。 如果您想要隱藏系結中的特定方法,您可以使用 屬性來裝飾 方法 [Internal]

此命令 btouch-native 將引進參考參數的檢查,以不為 Null。 如果您想要允許特定參數的 Null 值,請使用 [NullAllowed] 參數上的 屬性,如下所示:

[Export ("setText:")]
string SetText ([NullAllowed] string text);

匯出參考型別時,您也可以使用 [Export] 關鍵詞來指定配置語意。 這是確保不會外洩任何數據的必要專案。

繫結屬性

就像方法一樣, Objective-C 屬性會使用 系結 [Export] 屬性並直接對應至 C# 屬性。 就像方法一樣,屬性可以使用 裝飾 [Static][Internal] 屬性。

當您 [Export] 在 btouch-native 底下的 屬性上使用 屬性時,實際上會系結兩個方法:getter 和 setter。 您提供導出的名稱是basename,而 setter 會先加上 「set」 這個字,將basename的第一個字母變成大寫,讓選取器採用自變數來計算。 這表示在 [Export ("label")] 屬性上套用實際上會系結 “label” 和 “setLabel:” Objective-C 方法。

有時候屬性 Objective-C 不會遵循上述模式,而且會手動覆寫名稱。 在這些情況下,您可以使用 來控制系結產生的方式 [Bind] getter 或 setter 上的 屬性,例如:

[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }

接著,這會系結 「isMenuVisible」 和 「setMenuVisible:“。 您可以選擇性地使用下列語法系結屬性:

[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
  [Export ("name")]
  string Name();

  [Export("setName:")]
  void SetName(string name);
}

其中 getter 和 setter 明確定義為上述 和 setName 系結中的 name

除了使用 [Static]支援靜態屬性之外,您還可以使用 [IsThreadStatic]裝飾線程靜態屬性,例如:

[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }

就像方法允許使用 標記某些參數 [NullAllowed]一樣,您可以套用 [NullAllowed] 表示 null 是屬性的有效值,例如:

[Export ("text"), NullAllowed]
string Text { get; set; }

[NullAllowed]您也可以直接在 setter 上指定 參數:

[Export ("text")]
string Text { get; [NullAllowed] set; }

系結自定義控件的注意事項

設定自定義控件的系結時,應該考慮下列注意事項:

  1. 系結屬性必須是靜態 的 - 定義屬性的系結時, [Static] 必須使用 屬性。
  2. 屬性名稱必須完全符合 - 用來系結屬性的名稱必須完全符合自定義控件中的屬性名稱。
  3. 屬性類型必須完全 相符 - 用來系結屬性的變數類型必須完全符合自定義控件中的屬性類型。
  4. 斷點和 getter/setter - 永遠不會叫用屬性之 getter 或 setter 方法中的斷點。
  5. 觀察回 呼 - 您必須使用觀察回呼來通知自定義控制件的屬性值變更。

如果無法觀察上述任何列出的警告,可能會導致系結在運行時間以無訊息方式失敗。

Objective-C 可變模式和屬性

Objective-C 架構會使用成語,其中某些類別與可變動的子類別不可變。 例如 NSString ,是不可變的版本,而 NSMutableString 是允許突變的子類別。

在這些類別中,通常會看到不可變的基類包含具有 getter 的屬性,但沒有 setter。 以及要引進 setter 的可變版本。 由於 C# 並非真的可行,因此我們必須將此成語對應至可與 C# 搭配運作的成語。

這對應至 C# 的方式是將 getter 和 setter 同時新增至基類,但使用 將 setter 標示為 [NotImplemented] 屬性。

然後,在可變動的子類別上,您會使用 [Override] 屬性上的 屬性,以確保屬性實際上會覆寫父系的行為。

範例:

[BaseType (typeof (NSObject))]
interface MyTree {
    string Name { get; [NotImplemented] set; }
}

[BaseType (typeof (MyTree))]
interface MyMutableTree {
    [Override]
    string Name { get; set; }
}

系結建構函式

此工具 btouch-native 會自動為指定的類別 Foo產生類別中的四個建構函式,其會產生:

  • Foo ():預設建構函式 (對應至 Objective-C's “init” 建構函式)
  • Foo (NSCoder):在還原串行化 NIB 檔案期間使用的建構函式(對應至 Objective-C's “initWithCoder:” 建構函式)。
  • Foo (IntPtr handle):句柄型建立的建構函式,當運行時間需要從 Unmanaged 物件公開 Managed 物件時,運行時間會叫用此建構函式。
  • Foo (NSEmptyFlag):這是衍生類別用來防止雙重初始化。

對於您定義的建構函式,它們必須使用介面定義內的下列簽章來宣告:它們必須傳回 IntPtr 值,而且方法的名稱應該是建構函式。 例如,若要系結建 initWithFrame: 構函式,這就是您將使用的內容:

[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);

系結通訊協定

如 API 設計檔所述,在討論模型和通訊協定一節中,Xamarin.iOS 會將Objective-C通訊協議對應至已加上 旗標的類別[Model] 屬性。 這通常會在實作 Objective-C 委派類別時使用。

一般系結類別與委派類別之間的重大差異在於委派類別可能有一或多個選擇性方法。

例如,請考慮 類別 UIKit UIAccelerometerDelegate,這就是它在 Xamarin.iOS 中系結的方式:

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
        [Export ("accelerometer:didAccelerate:")]
        void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}

因為這是定義 UIAccelerometerDelegate 上的選擇性方法,因為沒有任何其他方法要做。 但是,如果通訊協定上有必要的方法,您應該新增 [Abstract] 方法的屬性。 這會強制實作的用戶實際提供 方法的主體。

一般而言,通訊協定會用於回應訊息的類別中。 這通常是 Objective-C 藉由將 響應通訊協定中方法的物件實例指派給 “delegate” 屬性來完成。

Xamarin.iOS 中的慣例是支援 Objective-C 鬆散結合的樣式,其中任何實例 NSObject 都可以指派給委派,也公開其強型別版本。 基於這個理由,我們通常會提供 Delegate 強型別和 WeakDelegate 鬆散類型的屬性。 我們通常會使用 [Export]系結鬆散類型版本,並使用 [Wrap] 屬性來提供強型別版本。

這會顯示如何將 類別系結 UIAccelerometer

[BaseType (typeof (NSObject))]
interface UIAccelerometer {
        [Static] [Export ("sharedAccelerometer")]
        UIAccelerometer SharedAccelerometer { get; }

        [Export ("updateInterval")]
        double UpdateInterval { get; set; }

        [Wrap ("WeakDelegate")]
        UIAccelerometerDelegate Delegate { get; set; }

        [Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
        NSObject WeakDelegate { get; set; }
}

MonoTouch 7.0 的新功能

從MonoTouch 7.0開始,已納入全新且改良的通訊協議系結功能。 這項新的支援可讓您更輕鬆地使用 Objective-C 慣用語,在指定的類別中採用一或多個通訊協定。

針對中的每個Objective-C通訊協定定義MyProtocol,現在有一個IMyProtocol介面會列出通訊協定中的所有必要方法,以及提供所有選擇性方法的擴充類別。 上述與 Xamarin Studio 編輯器中的新支持結合,可讓開發人員實作通訊協定方法,而不需要使用先前抽象模型類別的個別子類別。

包含 [Protocol] 屬性的任何定義實際上都會產生三個支持類別,以大幅改善您取用通訊協定的方式:

// Full method implementation, contains all methods
class MyProtocol : IMyProtocol {
    public void Say (string msg);
    public void Listen (string msg);
}

// Interface that contains only the required methods
interface IMyProtocol: INativeObject, IDisposable {
    [Export ("say:")]
    void Say (string msg);
}

// Extension methods
static class IMyProtocol_Extensions {
    public static void Optional (this IMyProtocol this, string msg);
    }
}

類別 作提供完整的抽象類,您可以覆寫 的個別方法並取得完整的型別安全性。 但由於 C# 不支援多重繼承,因此在某些情況下,您可能需要有不同的基類,但您仍想要實作介面,也就是

產生的 介面定義 會傳入。 它是具有通訊協定中所有必要方法的介面。 這可讓想要實作通訊協議的開發人員只實作 介面。 運行時間會自動將類型註冊為採用通訊協定。

請注意,介面只會列出必要的方法,而且會公開選擇性方法。 這表示採用通訊協議的類別會取得必要方法的完整類型檢查,但必須針對選擇性通訊協定方法使用弱型別(手動使用 [Export] 屬性並比對簽章)。

為了方便取用使用通訊協定的 API,系結工具也會產生擴充方法類別,以公開所有選擇性方法。 這表示只要您使用 API,您就能夠將通訊協定視為具有所有方法。

如果您想要在 API 中使用通訊協定定義,您必須在 API 定義中撰寫基本架構空白介面。 如果您想要在 API 中使用 MyProtocol,您需要執行此動作:

[BaseType (typeof (NSObject))]
[Model, Protocol]
interface MyProtocol {
    // Use [Abstract] when the method is defined in the @required section
    // of the protocol definition in Objective-C
    [Abstract]
    [Export ("say:")]
    void Say (string msg);

    [Export ("listen")]
    void Listen ();
}

interface IMyProtocol {}

[BaseType (typeof(NSObject))]
interface MyTool {
    [Export ("getProtocol")]
    IMyProtocol GetProtocol ();
}

之所以需要上述,是因為在系結時 IMyProtocol 不存在,這就是為什麼您需要提供空的介面。

採用通訊協議產生的介面

每當您實作針對通訊協議產生的其中一個介面時,如下所示:

class MyDelegate : NSObject, IUITableViewDelegate {
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

必要介面方法的實作會以適當的名稱導出,因此它相當於下列專案:

class MyDelegate : NSObject, IUITableViewDelegate {
    [Export ("getRowHeight:")]
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

這適用於所有必要的通訊協議成員,但有選擇性選取器需要注意的特殊案例。 使用基類時,選擇性通訊協議成員會以相同方式處理:

public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
	public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

但在使用通訊協定介面時,需要新增 [Export]。 當您從覆寫開始新增它時,IDE 會透過自動完成來新增它。

public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
	[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
	public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

兩者在運行時間有輕微的行為差異。

  • 基類的使用者(例如 NSUrlSessionDownloadDelegate)提供所有必要的和選擇性選取器,並傳回合理的預設值。
  • 介面的使用者(例如 INSUrlSessionDownloadDelegate)只會回應所提供的確切選取器。

某些罕見的類別在這裏的行為可能會有所不同。 不過,在幾乎所有情況下,使用都安全。

系結類別延伸模組

在 Objective-C 中,可以透過新的方法擴充類別,與 C# 的擴充方法類似。 當其中一個方法存在時,您可以使用 [BaseType] 屬性,將方法標示為訊息的 Objective-C 接收者。

例如,在 Xamarin.iOS 中,我們會系結在 NSString 匯入 為 中NSStringDrawingExtensions方法時UIKit所定義的擴充方法,如下所示:

[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
    [Export ("drawAtPoint:withFont:")]
    CGSize DrawString (CGPoint point, UIFont font);
}

系結 Objective-C 自變數清單

Objective-C 支援 variadic 自變數。 例如:

- (void) appendWorkers:(XWorker *) firstWorker, ...
  NS_REQUIRES_NIL_TERMINATION ;

若要從 C# 叫用此方法,您要想要建立如下所示的簽章:

[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)

這會將方法宣告為內部,並隱藏上述 API 給使用者,但將其公開至連結庫。 然後,您可以撰寫如下的方法:

public void AppendWorkers(params Worker[] workers)
{
    if (workers is null)
         throw new ArgumentNullException ("workers");

    var pNativeArr = Marshal.AllocHGlobal(workers.Length * IntPtr.Size);
    for (int i = 1; i < workers.Length; ++i)
        Marshal.WriteIntPtr (pNativeArr, (i - 1) * IntPtr.Size, workers[i].Handle);

    // Null termination
    Marshal.WriteIntPtr (pNativeArr, (workers.Length - 1) * IntPtr.Size, IntPtr.Zero);

    // the signature for this method has gone from (IntPtr, IntPtr) to (Worker, IntPtr)
    WorkerManager.AppendWorkers(workers[0], pNativeArr);
    Marshal.FreeHGlobal(pNativeArr);
}

系結欄位

有時候,您會想要存取連結庫中所宣告的公用字段。

這些欄位通常包含必須參考的字串或整數值。 它們通常用來表示特定通知的字串,以及做為字典中的索引鍵。

若要系結欄位,請將屬性新增至介面定義檔,並使用 屬性裝飾屬性 [Field] 。 此屬性會採用一個參數:要查閱之符號的 C 名稱。 例如:

[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }

如果您想要將不衍生自 NSObject的靜態類別中包裝各種欄位,您可以使用 [Static] 類別上的 屬性,如下所示:

[Static]
interface LonelyClass {
    [Field ("NSSomeEventNotification")]
    NSString NSSomeEventNotification { get; }
}

上述會產生 LonelyClass ,其不會衍生自 NSObject ,且將包含公開為 NSString的系結NSSomeEventNotificationNSString

屬性 [Field] 可以套用至下列資料類型:

  • NSString 參考 (僅限唯讀屬性)
  • NSArray 參考 (僅限唯讀屬性)
  • 32 位 ints (System.Int32
  • 64 位 ints (System.Int64
  • 32 位浮點數 (System.Single
  • 64 位浮點數 (System.Double
  • System.Drawing.SizeF
  • CGSize

除了原生功能變數名稱之外,您可以傳遞連結庫名稱來指定字位所在的連結庫名稱:

[Static]
interface LonelyClass {
    [Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
    NSString SomeSharedLibrarySymbol { get; }
}

如果您以靜態方式連結,則沒有任何連結庫可繫結至,因此您必須使用 __Internal 名稱:

[Static]
interface LonelyClass {
    [Field ("MyFieldFromALibrary", "__Internal")]
    NSString MyFieldFromALibrary { get; }
}

系結列舉

您可以直接在系結檔案中新增 enum ,使其更容易在 API 定義內使用,而不需使用不同的原始程式檔(必須在系結和最終專案中編譯)。

範例:

[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}

interface MyType {
    [Export ("initWithEnum:")]
    IntPtr Constructor (MyEnum value);
}

您也可以建立自己的列舉來取代 NSString 常數。 在此情況下,產生器會自動建立方法來為您轉換列舉值和NSString 常數。

範例:

enum NSRunLoopMode {

    [DefaultEnumValue]
    [Field ("NSDefaultRunLoopMode")]
    Default,

    [Field ("NSRunLoopCommonModes")]
    Common,

    [Field (null)]
    Other = 1000
}

interface MyType {
    [Export ("performForMode:")]
    void Perform (NSString mode);

    [Wrap ("Perform (mode.GetConstant ())")]
    void Perform (NSRunLoopMode mode);
}

在上述範例中,您可以決定使用 [Internal] 屬性裝飾void Perform (NSString mode);這會隱藏系結取用者中的常數型 API。

不過,這會限制子類別化類型,因為較好的 API 替代專案會使用 [Wrap] 屬性。 這些產生的方法不是 virtual,也就是您無法覆寫它們,這可能是一個很好的選擇。

替代方法是將原始的定義 NSString標示為 [Protected]。 這可讓子類別化在必要時運作,而且包裝的版本仍可運作,並呼叫 overriden 方法。

將、 NSNumberNSString 系結NSValue至較佳的類型

屬性[BindAs]允許將 和 NSValue NSString(列舉) 系結NSNumber至更精確的 C# 類型。 屬性可用來透過原生 API 建立更好、更精確的 .NET API。

您可以使用 裝飾方法(在傳回值上)、參數和屬性 [BindAs]。 唯一的限制是您的成員 不得 位於 內 [Protocol][Model] 介面。

例如:

[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);

會輸出:

[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }

在內部,我們將執行 bool?<-NSNumber> 和<CGRect ->NSValue 轉換。

[BindAs]也支援和NSStringNSNumberNSValue陣列舉(列舉)。

例如:

[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }

會輸出:

[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }

CAScrollNSString是支援的列舉,我們將擷取正確的NSString值並處理類型轉換。

請參閱檔 [BindAs] ,以查看支援的轉換類型。

系結通知

通知是張貼至 的 NSNotificationCenter.DefaultCenter 訊息,並做為將訊息從應用程式某個部分廣播到另一個部分的機制。 開發人員通常會使用 NSNotificationCenterAddObserver 方法訂閱通知。 當應用程式將訊息張貼至通知中心時,通常會包含儲存在 NSNotification.UserInfo 字典中的承載。 此字典是弱型別,而且從中取得資訊很容易出錯,因為使用者通常需要在文件中閱讀字典上可用的索引鍵,以及可儲存在字典中的值類型。 有時索引鍵的存在也會當做布爾值使用。

Xamarin.iOS 系結產生器可支持開發人員系結通知。 若要這樣做,您可以設定 [Notification] 屬性上的 屬性,該屬性也已使用標記 [Field] property (它可以是公用或私用的)。

這個屬性可以在沒有自變數的情況下用於沒有承載的通知,或者您可以指定 System.Type 參考 API 定義中另一個介面的,通常是以 “EventArgs” 結尾的名稱。 產生器會將 介面轉換成子類別的 EventArgs 類別,並將包含該處所列的所有屬性。 屬性 [Export] 應該用於 EventArgs 類別,以列出用來查閱 Objective-C 字典以擷取值之索引鍵的名稱。

例如:

interface MyClass {
    [Notification]
    [Field ("MyClassDidStartNotification")]
    NSString DidStartNotification { get; }
}

上述程式代碼會產生具有下列方法的巢狀類別 MyClass.Notifications

public class MyClass {
   [..]
   public Notifications {
      public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
   }
}

然後,程式代碼的使用者可以使用類似下列程式代碼,輕鬆地訂閱張貼至 NSDefaultCenter通知:

var token = MyClass.Notifications.ObserverDidStart ((notification) => {
    Console.WriteLine ("Observed the 'DidStart' event!");
});

ObserveDidStart 回的值可用來輕鬆地停止接收通知,如下所示:

token.Dispose ();

或者,您可以呼叫 NSNotification.DefaultCenter.RemoveObserver 並傳遞令牌。 如果您的通知包含參數,您應該指定協助程式 EventArgs 介面,如下所示:

interface MyClass {
    [Notification (typeof (MyScreenChangedEventArgs)]
    [Field ("MyClassScreenChangedNotification")]
    NSString ScreenChangedNotification { get; }
}

// The helper EventArgs declaration
interface MyScreenChangedEventArgs {
    [Export ("ScreenXKey")]
    nint ScreenX { get; set; }

    [Export ("ScreenYKey")]
    nint ScreenY { get; set; }

    [Export ("DidGoOffKey")]
    [ProbePresence]
    bool DidGoOff { get; }
}

上述會分別使用索引鍵 「ScreenXKey」 和 「ScreenYKey」 來產生 MyScreenChangedEventArgs 類別,其中包含 ScreenXScreenY 屬性,以分別從 NSNotification.UserInfo 字典擷取數據,並套用適當的轉換。 屬性 [ProbePresence] 會用於產生器,以在 中 UserInfo設定索引鍵,而不是嘗試擷取值。 這用於索引鍵存在是值的情況(通常是布爾值)。

這可讓您撰寫如下的程式代碼:

var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
    Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});

系結類別

類別是一種 Objective-C 機制,可用來擴充類別中可用的方法和屬性集。 在實務上,當特定架構在 中連結時,它們會用來擴充基類的功能(例如NSObjectUIKit),使其方法可供使用,但只有在新架構已連結時。 在其他某些情況下,它們用來依功能來組織類別中的功能。 它們與 C# 擴充方法類似。這就是類別在 中的 Objective-C外觀:

@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end

如果在連結庫上找到,則上述範例會使用 方法 makeBackgroundRed擴充的實例UIView

若要系結這些屬性,您可以在 [Category] 介面定義上使用 屬性。 使用 時 [Category] 屬性,其意義 [BaseType] 屬性會從用來指定要擴充的基類變更為要擴充的類型。

以下顯示延伸模組如何 UIView 系結並轉換成 C# 擴充方法:

[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
    [Export ("makeBackgroundRed")]
    void MakeBackgroundRed ();
}

上述會建立包含 MyUIViewExtension 擴充方法的 MakeBackgroundRed 類別。 這表示您現在可以在任何子類別上 UIView 呼叫 「MakeBackgroundRed」,並提供您取得 Objective-C的相同功能。 在其他某些情況下,類別不會用來擴充系統類別,而是為了組織功能,純粹是為了裝飾目的。 與下列類似:

@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end

@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end

雖然您可以使用 [Category] 屬性也適用於宣告的這個裝飾樣式,您也可以將它們全部新增至類別定義。 這兩者都會達到相同的目的:

[BaseType (typeof (NSObject))]
interface SocialNetworking {
}

[Category]
[BaseType (typeof (SocialNetworking))]
interface Twitter {
    [Export ("postToTwitter:")]
    void PostToTwitter (Message message);
}

[Category]
[BaseType (typeof (SocialNetworking))]
interface Facebook {
    [Export ("postToFacebook:andPicture:")]
    void PostToFacebook (Message message, UIImage picture);
}

在這些情況下,合併類別會比較短:

[BaseType (typeof (NSObject))]
interface SocialNetworking {
    [Export ("postToTwitter:")]
    void PostToTwitter (Message message);

    [Export ("postToFacebook:andPicture:")]
    void PostToFacebook (Message message, UIImage picture);
}

系結區塊

區塊是 Apple 引進的新建構,可將 C# 匿名方法的功能對等專案帶入 Objective-C。 例如,類別 NSSet 現在會公開這個方法:

- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block

上述描述會宣告名為 enumerateObjectsUsingBlock: 的方法,該方法接受名為 block的一個自變數。 這個區塊類似於 C# 匿名方法,因為它支援擷取目前的環境(“this” 指標,存取局部變數和參數)。 中的 NSSet 上述方法會使用兩個參數叫 NSObject 用 區塊, id obj 以及布爾值 ( BOOL *stop) 元件的指標。

若要將這類 API 與 btouch 系結,您必須先將區塊類型簽章宣告為 C# 委派,然後從 API 進入點參考它,如下所示:

// This declares the callback signature for the block:
delegate void NSSetEnumerator (NSObject obj, ref bool stop)

// Later, inside your definition, do this:
[Export ("enumerateObjectUsingBlock:")]
void Enumerate (NSSetEnumerator enum)

現在,您的程式代碼可以從 C# 呼叫函式:

var myset = new NSMutableSet ();
myset.Add (new NSString ("Foo"));

s.Enumerate (delegate (NSObject obj, ref bool stop){
    Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});

如果您偏好使用 Lambda,您也可以使用 Lambda:

var myset = new NSMutableSet ();
mySet.Add (new NSString ("Foo"));

s.Enumerate ((obj, stop) => {
    Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});

非同步方法

系結產生器可以將特定方法類別轉換成異步易記的方法(傳回Task或Task<T>的方法)。

您可以使用 [Async] 傳回 void 且其最後一個自變數為回呼之方法上的屬性。 當您將這個套用至方法時,系結產生器會產生具有 後綴 Async的該方法版本。 如果回呼不接受任何參數,則傳回值會是 Task,如果回呼採用參數,則結果會是 Task<T>。 如果回呼採用多個參數,您應該設定 ResultTypeResultTypeName 來指定所產生型別的所需名稱,以保存所有屬性。

範例:

[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);

上述程式代碼將同時產生LoadFile方法,以及:

[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);

顯示弱式 NSDictionary 參數的強型別

在 API 的許多 Objective-C 位置,參數會以具有特定索引鍵和值的弱型 NSDictionary 別 API 的形式傳遞,但這些是容易出錯的(您可以傳遞無效的索引鍵,而且沒有警告;您可以傳遞無效的值,而且沒有警告),並令人沮喪地使用,因為它們需要多次前往檔來查閱可能的索引鍵名稱和值。

解決方案是提供強型別版本,以提供 API 的強型別版本,並在幕後對應各種基礎索引鍵和值。

因此,例如,如果 Objective-C API 接受 NSDictionary ,且其記載為採用磁碟區值從 0.0 到 1.0 的 索引鍵NSNumberXyzVolumeKey,以及XyzCaptionKey採用字串的 ,則您希望您的使用者擁有如下所示的好 API:

public class  XyzOptions {
    public nfloat? Volume { get; set; }
    public string Caption { get; set; }
}

屬性 Volume 定義為可為 Null 的浮點數,因為中的 Objective-C 慣例不需要這些字典具有 值,因此在某些情況下可能無法設定該值。

若要這樣做,您需要執行一些動作:

  • 建立強型別類別,子類別 DictionaryContainer 並提供每個屬性的各種 getter 和 setter。
  • 宣告採用 NSDictionary 新強型別版本之方法的多載。

您可以手動建立強型別類別,或使用產生器為您執行工作。 我們會先探索如何手動執行這項操作,以便您了解發生了什麼事,然後探索自動方法。

您需要為此建立支援檔案,它不會進入您的合約 API。 這是您必須撰寫以建立 XyzOptions 類別的內容:

public class XyzOptions : DictionaryContainer {
# if !COREBUILD
    public XyzOptions () : base (new NSMutableDictionary ()) {}
    public XyzOptions (NSDictionary dictionary) : base (dictionary){}

    public nfloat? Volume {
       get { return GetFloatValue (XyzOptionsKeys.VolumeKey); }
       set { SetNumberValue (XyzOptionsKeys.VolumeKey, value); }
    }
    public string Caption {
       get { return GetStringValue (XyzOptionsKeys.CaptionKey); }
       set { SetStringValue (XyzOptionsKeys.CaptionKey, value); }
    }
# endif
}

然後,您應該提供包裝函式方法,以在低階 API 之上呈現高階 API。

[BaseType (typeof (NSObject))]
interface XyzPanel {
    [Export ("playback:withOptions:")]
    void Playback (string fileName, [NullAllowed] NSDictionary options);

    [Wrap ("Playback (fileName, options?.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

如果您的 API 不需要覆寫,您可以使用 安全地隱藏以 NSDictionary 為基礎的 API [Internal] 屬性。

如您所見,我們使用 [Wrap] 屬性可呈現新的 API 進入點,而我們會使用強型別類別 XyzOptions 來呈現它。 包裝函式方法也允許傳遞 Null。

現在,我們未提及的一件事就是 XyzOptionsKeys 值的來源。 您通常會將 API 出現在靜態類別中的金鑰分組,如下所示 XyzOptionsKeys

[Static]
class XyzOptionKeys {
    [Field ("kXyzVolumeKey")]
    NSString VolumeKey { get; }

    [Field ("kXyzCaptionKey")]
    NSString CaptionKey { get; }
}

讓我們看看建立這些強型別字典的自動支援。 這可避免大量的未定案,而且您可以直接在 API 合約中定義字典,而不是使用外部檔案。

若要建立強型別字典,請在 API 中引進介面,並使用 StrongDictionary 屬性裝飾它。 這會告訴產生器,它應該建立與介面相同名稱的類別,該類別會衍生自 DictionaryContainer ,並且會為其提供強型別存取子。

屬性 [StrongDictionary] 會採用一個參數,這是包含字典索引鍵的靜態類別名稱。 然後,介面的每個屬性都會變成強型別存取子。 根據預設,程式代碼會使用屬性的名稱搭配靜態類別中的後綴 「Key」 來建立存取子。

這表示建立強型別存取子不再需要外部檔案,也不需要手動為每個屬性建立 getter 和 setter,也不需要自行手動查閱索引鍵。

這就是整個系結的外觀:

[Static]
class XyzOptionKeys {
    [Field ("kXyzVolumeKey")]
    NSString VolumeKey { get; }

    [Field ("kXyzCaptionKey")]
    NSString CaptionKey { get; }
}
[StrongDictionary ("XyzOptionKeys")]
interface XyzOptions {
    nfloat Volume { get; set; }
    string Caption { get; set; }
}

[BaseType (typeof (NSObject))]
interface XyzPanel {
    [Export ("playback:withOptions:")]
    void Playback (string fileName, [NullAllowed] NSDictionary options);

    [Wrap ("Playback (fileName, options?.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

如果您需要在成員中 XyzOption 參考不同的欄位(不是後綴 Key的屬性名稱),您可以使用 裝飾屬性 [Export] 屬性,其名稱為您想要使用的名稱。

型別對應

本節涵蓋 Objective-C 如何將類型對應至 C# 類型。

簡單型別

下表說明如何將 和 CocoaTouch 世界的型 Objective-C 別對應至 Xamarin.iOS 世界:

Objective-C 類型名稱 Xamarin.iOS 整合 API 類型
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString更多關於系結 NSString string
char * string(另請參閱: [PlainString]
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
CoreFoundation 型態 (CF* CoreFoundation.CF*
GLint nint
GLfloat nfloat
基礎類型 (NS* Foundation.NS*
id Foundation.NSObject
NSGlyph nint
NSSize CGSize
NSTextAlignment UITextAlignment
SEL ObjCRuntime.Selector
dispatch_queue_t CoreFoundation.DispatchQueue
CFTimeInterval double
CFIndex nint
NSGlyph nuint

陣列

Xamarin.iOS 執行時間會自動處理將 C# 陣列NSArrays轉換成 和進行轉換,例如傳回 NSArrayUIViews虛數Objective-C方法:

// Get the peer views - untyped
- (NSArray *)getPeerViews ();

// Set the views for this container
- (void) setViews:(NSArray *) views

系結如下:

[Export ("getPeerViews")]
UIView [] GetPeerViews ();

[Export ("setViews:")]
void SetViews (UIView [] views);

其概念是使用強型別 C# 數位列,因為這樣可讓 IDE 以實際類型提供適當的程式代碼完成,而不需要強制使用者猜測,或查閱檔以找出數位中包含的物件實際類型。

如果無法追蹤陣列中包含的實際衍生類型,您可以使用 NSObject [] 做為傳回值。

選取器

選取器會顯示在 Objective-C API 上做為特殊類型 SEL。 系結選取器時,您會將類型對應至 ObjCRuntime.Selector。 通常會在 API 中公開選取器,其中包含物件、目標物件,以及要叫用目標物件中的選取器。 提供這兩者基本上會對應至 C# 委派:封裝要叫用的方法,以及要叫用方法的物件。

這就是系結的外觀:

interface Button {
   [Export ("setTarget:selector:")]
   void SetTarget (NSObject target, Selector sel);
}

而這就是方法通常會在應用程式中使用的方式:

class DialogPrint : UIViewController {
    void HookPrintButton (Button b)
    {
        b.SetTarget (this, new Selector ("print"));
    }

    [Export ("print")]
    void ThePrintMethod ()
    {
       // This does the printing
    }
}

若要讓系結更適用於 C# 開發人員,您通常會提供採用 NSAction 參數的方法,讓 C# 委派和 Lambda 使用,而不是 Target+Selector。 若要這樣做,您通常會使用標記方法來隱藏 SetTarget 方法 [Internal] 屬性,然後您會公開新的協助程式方法,如下所示:

// API.cs
interface Button {
   [Export ("setTarget:selector:"), Internal]
   void SetTarget (NSObject target, Selector sel);
}

// Extensions.cs
public partial class Button {
     public void SetTarget (NSAction callback)
     {
         SetTarget (new NSActionDispatcher (callback), NSActionDispatcher.Selector);
     }
}

因此,現在您的使用者程式代碼可以撰寫如下:

class DialogPrint : UIViewController {
    void HookPrintButton (Button b)
    {
        // First Style
        b.SetTarget (ThePrintMethod);

        // Lambda style
        b.SetTarget (() => {  /* print here */ });
    }

    void ThePrintMethod ()
    {
       // This does the printing
    }
}

字串

當您系結採用 NSString的方法時,可以在傳回型別和參數上,將 它取代為 C# 字串類型。

唯一 NSString 想要直接使用 的情況是當字串當做令牌使用時。 如需字串和 NSString的詳細資訊,請參閱 NSString 上的 API 設計檔。

在某些情況下,API 可能會公開類似 C 的字串 (char *) 而不是 Objective-C 字串 (NSString *)。 在這些情況下,您可以使用標註 參數 [PlainString] 屬性。

out/ref 參數

某些 API 會傳回其參數中的值,或以傳址方式傳遞參數。

簽章一般看起來像這樣:

- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref

第一個範例顯示傳回錯誤碼的常見 Objective-C 慣用語、傳遞指標的指標 NSError ,並在傳回時設定值。 第二種方法顯示方法如何 Objective-C 接受 物件並修改其內容。 這是以傳址方式傳遞,而不是純輸出值。

您的系結看起來會像這樣:

[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);

記憶體管理屬性

當您使用 [Export] 屬性並傳遞由呼叫方法保留的數據時,您可以藉由將自變數語意傳遞為第二個參數來指定自變數語意,例如:

[Export ("method", ArgumentSemantic.Retain)]

上述會將值標示為具有「保留」語意。 可用的語意如下:

  • 指派
  • 複本
  • 保留

樣式方針

使用 [內部]

您可以使用 [Internal] 屬性可隱藏公用 API 中的方法。 如果公開的 API 太低層級,而且您想要根據此方法在不同的檔案中提供高階實作,您可能會想要這樣做。

當您在系結產生器中遇到限制時,您也可以使用這個方法,例如,某些進階案例可能會公開未系結的類型,而您想要以自己的方式系結這些類型,而您想要以自己的方式包裝這些類型。

事件處理程式和回呼

Objective-C 類別通常會透過在委派類別 (Objective-C delegate) 上傳送訊息來廣播通知或要求資訊。

此模型雖然完全支援且由 Xamarin.iOS 浮出水面,但有時可能很麻煩。 Xamarin.iOS 會在類別上公開 C# 事件模式和方法回呼系統,而這些類別可用於這些情況。 這可讓這類程式代碼執行:

button.Clicked += delegate {
    Console.WriteLine ("I was clicked");
};

系結產生器能夠減少將模式對應 Objective-C 至 C# 模式所需的輸入量。

從 Xamarin.iOS 1.4 開始,您也可以指示產生器產生特定 Objective-C 委派的系結,並將委派公開為主機類型上的 C# 事件和屬性。

此程式涉及兩個類別,主機類別是目前發出事件並將事件傳送至 DelegateWeakDelegate 和實際委派類別的類別。

請考慮下列設定:

[BaseType (typeof (NSObject))]
interface MyClass {
    [Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
    NSObject WeakDelegate { get; set; }

    [Wrap ("WeakDelegate")][NullAllowed]
    MyClassDelegate Delegate { get; set; }
}

[BaseType (typeof (NSObject))]
interface MyClassDelegate {
    [Export ("loaded:bytes:")]
    void Loaded (MyClass sender, int bytes);
}

若要包裝類別,您必須:

  • 在您的主機類別中,將 新增至 [BaseType]
    宣告做為其委派的型別,以及您公開的 C# 名稱。 在上述範例中,分別為 typeof (MyClassDelegate)WeakDelegate
  • 在委派類別中,在每個具有兩個以上參數的方法上,您必須指定您要用於自動產生 EventArgs 類別的類型。

系結產生器不限於只包裝單一事件目的地,某些 Objective-C 類別可能會將訊息發出至多個委派,因此您必須提供陣列來支援此設定。 大部分的設定都不需要它,但產生器已準備好支持這些情況。

產生的程式代碼會是:

[BaseType (typeof (NSObject),
    Delegates=new string [] {"WeakDelegate"},
    Events=new Type [] { typeof (MyClassDelegate) })]
interface MyClass {
        [Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
        NSObject WeakDelegate { get; set; }

        [Wrap ("WeakDelegate")][NullAllowed]
        MyClassDelegate Delegate { get; set; }
}

[BaseType (typeof (NSObject))]
interface MyClassDelegate {
        [Export ("loaded:bytes:"), EventArgs ("MyClassLoaded")]
        void Loaded (MyClass sender, int bytes);
}

EventArgs用來指定要產生之EventArgs類別的名稱。 您應該針對每個簽章使用一個簽章(在此範例中, EventArgs 將會包含 With nint類型的屬性)。

使用上述定義,產生器會在產生的 MyClass 中產生下列事件:

public MyClassLoadedEventArgs : EventArgs {
        public MyClassLoadedEventArgs (nint bytes);
        public nint Bytes { get; set; }
}

public event EventHandler<MyClassLoadedEventArgs> Loaded {
        add; remove;
}

因此,您現在可以使用如下的程式代碼:

MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
        Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};

回呼就像事件調用一樣,差別在於沒有多個潛在訂閱者(例如,多個方法可以連結至事件或DownloadFinished事件)回呼只能有單一Clicked訂閱者。

程式完全相同,唯一的差異在於,除了公開將產生的類別名稱 EventArgs 外,EventArgs 實際上用來命名產生的 C# 委派名稱。

如果委派類別中的 方法傳回值,系結產生器會將此值對應至父類別中的委派方法,而不是事件。 在這些情況下,如果使用者未連結委派,您必須提供方法應該傳回的預設值。 您可以使用 來執行此動作 [DefaultValue][DefaultValueFromArgument] 屬性。

[DefaultValue] 會硬式編碼傳回值,而 [DefaultValueFromArgument] 用來指定將傳回哪些輸入自變數。

列舉和基底類型

您也可以參考 btouch 介面定義系統未直接支援的列舉或基底類型。 若要這樣做,請將您的列舉和核心類型放入個別的檔案中,並納入此檔案,作為您提供給 btouch 的額外檔案的一部分。

連結相依性

如果您是系結不屬於您應用程式的 API,您必須確定可執行檔已連結至這些連結庫。

您必須通知 Xamarin.iOS 如何連結連結您的連結庫,這可以藉由改變組建組態來 mtouch 叫用命令,並搭配一些額外的建置自變數叫用命令,以指定如何使用 “-gcc_flags” 選項與新的連結庫連結,後面接著引號字串,其中包含程式所需的所有額外連結庫。 喜歡這個:

-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"

上述範例會將、 libSystemLibrary.dylibCFNetwork 架構連結庫連結libMyLibrary.a至您的最終可執行檔。

或者,您可以利用元件層級 [LinkWithAttribute],以內嵌在合約檔案中(例如 )。AssemblyInfo.cs 當您使用 [LinkWithAttribute]時,您必須在進行系結時提供原生連結庫,因為這會內嵌原生連結庫與您的應用程式。 例如:

// Specify only the library name as a constructor argument and specify everything else with properties:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget = LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]

// Or you can specify library name *and* link target as constructor arguments:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]

您可能想知道,為什麼您需要 -force_load 命令,原因在於 -ObjC 旗標雖然編譯中的程序代碼,但它不會保留支持類別所需的元數據(鏈接器/編譯程式無效程式代碼刪除刪除區),這是您在運行時間 Xamarin.iOS 所需的。

輔助參考

某些暫時性物件,例如動作表和警示方塊很麻煩,可追蹤開發人員,而且系結產生器可以在這裡有所説明。

例如,如果您有一個類別顯示訊息,然後產生 Done 事件,則處理此作業的傳統方式如下:

class Demo {
    MessageBox box;

    void ShowError (string msg)
    {
        box = new MessageBox (msg);
        box.Done += { box = null; ... };
    }
}

在上述案例中,開發人員必須保留物件本身的參考,並自行洩漏或主動清除方塊的參考。 在系結程式代碼時,產生器支援追蹤您參考,並在叫用特殊方法時加以清除,然後上述程式代碼會變成:

class Demo {
    void ShowError (string msg)
    {
        var box = new MessageBox (msg);
        box.Done += { ... };
    }
}

請注意,不再需要將變數保留在 實例中、它與局部變數搭配運作,而且不需要在物件死去時清除參考。

若要利用這項功能,您的類別應該在宣告中 [BaseType] 設定 Events 屬性,而且 KeepUntilRef 變數也會設定為物件完成其工作時叫用的方法名稱,如下所示:

[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
    [Export ("show")]
    void Show (string message);
}

繼承通訊協定

從 Xamarin.iOS v3.2 開始,我們支援繼承自已標示為 屬性的 [Model] 通訊協定。 這在某些 API 模式中很有用,例如通訊協定繼承自MKAnnotation通訊協定的位置MapKitMKOverlay,並由繼承自NSObject的一些類別採用。

在過去,我們需要將通訊協定複製到每個實作,但在這些情況下,我們可以讓 MKShape 類別繼承自 MKOverlay 通訊協定,而且它會自動產生所有必要的方法。