Bindungsbibliotheken Objective-C

Wenn Sie mit Xamarin.iOS oder Xamarin.Mac arbeiten, treten möglicherweise Fälle auf, in denen Sie eine Drittanbieterbibliothek Objective-C nutzen möchten. In diesen Situationen können Sie Xamarin-Bindungsprojekte verwenden, um eine C#-Bindung für die nativen Objective-C Bibliotheken zu erstellen. Das Projekt verwendet dieselben Tools, die wir verwenden, um die iOS- und Mac-APIs in C# zu bringen.

In diesem Dokument wird beschrieben, wie APIs gebunden Objective-C werden. Wenn Sie nur C-APIs binden, sollten Sie dafür den .NET-Standardmechanismus verwenden, das P/Invoke-Framework. Details zum statischen Verknüpfen einer C-Bibliothek finden Sie auf der Seite Verknüpfen nativer Bibliotheken .

Weitere Informationen finden Sie in unserem Begleitenden Referenzhandbuch zu Bindungstypen. Wenn Sie mehr darüber erfahren möchten, was unter der Haube passiert, besuchen Sie unsere Seite Bindungsübersicht .

Bindungen können sowohl für iOS- als auch für Mac-Bibliotheken erstellt werden. Auf dieser Seite wird beschrieben, wie Sie an einer iOS-Bindung arbeiten, mac-Bindungen sind jedoch sehr ähnlich.

Beispielcode für iOS

Sie können das iOS-Bindungsbeispielprojekt verwenden, um mit Bindungen zu experimentieren.

Erste Schritte

Die einfachste Möglichkeit zum Erstellen einer Bindung besteht darin, ein Xamarin.iOS-Bindungsprojekt zu erstellen. Sie können dies über Visual Studio für Mac tun, indem Sie den Projekttyp iOS-Bibliotheksbindungsbibliothek >>auswählen:

Führen Sie dazu Visual Studio für Mac aus, indem Sie den Projekttyp iOS-Bibliotheksbindungsbibliothek auswählen.

Das generierte Projekt enthält eine kleine Vorlage, die Sie bearbeiten können, und enthält zwei Dateien: ApiDefinition.cs und StructsAndEnums.cs.

In ApiDefinition.cs definieren Sie den API-Vertrag. Dies ist die Datei, die beschreibt, wie die zugrunde liegende Objective-C API in C# projiziert wird. Die Syntax und der Inhalt dieser Datei sind das Standard Diskussionsthema dieses Dokuments, und der Inhalt dieses Dokuments ist auf C#-Schnittstellen und C#-Delegatdeklarationen beschränkt. Die StructsAndEnums.cs Datei ist die Datei, in der Sie alle Definitionen eingeben, die für die Schnittstellen und Delegaten erforderlich sind. Dies schließt Enumerationswerte und Strukturen ein, die Ihr Code möglicherweise verwendet.

Binden einer API

Um eine umfassende Bindung zu erstellen, sollten Sie die Objective-C API-Definition verstehen und sich mit den Richtlinien für .NET Framework Design vertraut machen.

Um Ihre Bibliothek zu binden, beginnen Sie in der Regel mit einer API-Definitionsdatei. Eine API-Definitionsdatei ist lediglich eine C#-Quelldatei, die C#-Schnittstellen enthält, die mit einer Handvoll Attribute versehen wurden, die die Bindung unterstützen. Diese Datei definiert den Vertrag zwischen C# und Objective-C .

Dies ist beispielsweise eine triviale API-Datei für eine Bibliothek:

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);
  }
}

Im obigen Beispiel wird eine Klasse namens Cocos2D.Camera definiert, die NSObject vom Basistyp abgeleitet wird (dieser Typ stammt von Foundation.NSObject) und die eine statische Eigenschaft (ZEye), zwei Methoden definiert, die keine Argumente übernehmen, und eine Methode, die drei Argumente akzeptiert.

Eine ausführliche Erläuterung des Formats der API-Datei und der Attribute, die Sie verwenden können, finden Sie im Abschnitt API-Definitionsdatei unten.

Um eine vollständige Bindung zu erstellen, beschäftigen Sie sich in der Regel mit vier Komponenten:

  • Die API-Definitionsdatei (ApiDefinition.cs in der Vorlage).
  • Optional: alle Enumerationen, Typen und Strukturen, die für die API-Definitionsdatei (StructsAndEnums.cs in der Vorlage) erforderlich sind.
  • Optional: zusätzliche Quellen, die die generierte Bindung erweitern oder eine C#-freundlicheRE API bereitstellen (alle C#-Dateien, die Sie dem Projekt hinzufügen).
  • Die native Bibliothek, die Sie binden.

Dieses Diagramm zeigt die Beziehung zwischen den Dateien:

Dieses Diagramm zeigt die Beziehung zwischen den Dateien.

Die API-Definitionsdatei enthält nur Namespaces und Schnittstellendefinitionen (mit allen Membern, die eine Schnittstelle enthalten kann), und darf keine Klassen, Enumerationen, Delegaten oder Strukturen enthalten. Die API-Definitionsdatei ist lediglich der Vertrag, der zum Generieren der API verwendet wird.

Jeder zusätzliche Code, den Sie benötigen, z. B. Enumerationen oder unterstützende Klassen, sollte in einer separaten Datei gehostet werden. Im obigen Beispiel ist "CameraMode" ein Enumerationswert, der in der CS-Datei nicht vorhanden ist und in einer separaten Datei gehostet werden sollte, z. B. StructsAndEnums.cs:

public enum CameraMode {
    FlyOver, Back, Follow
}

Die APIDefinition.cs Datei wird mit der StructsAndEnum -Klasse kombiniert und wird verwendet, um die Kernbindung der Bibliothek zu generieren. Sie können die resultierende Bibliothek unverändert verwenden, aber in der Regel sollten Sie die resultierende Bibliothek optimieren, um einige C#-Features zum Nutzen Ihrer Benutzer hinzuzufügen. Einige Beispiele sind das Implementieren einer ToString() Methode, das Bereitstellen von C#-Indexern, das Hinzufügen impliziter Konvertierungen zu und aus einigen nativen Typen oder das Bereitstellen stark typisierter Versionen einiger Methoden. Diese Verbesserungen werden in zusätzlichen C#-Dateien gespeichert. Fügen Sie ihrem Projekt lediglich die C#-Dateien hinzu, die in diesen Buildprozess einbezogen werden.

Dies zeigt, wie Sie den Code in Ihrer Extra.cs Datei implementieren würden. Beachten Sie, dass Sie partielle Klassen verwenden, da diese die partiellen Klassen erweitern, die aus der Kombination der Kernbindung und der ApiDefinition.csStructsAndEnums.cs Kernbindung generiert werden:

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

Durch das Erstellen der Bibliothek wird Ihre native Bindung erzeugt.

Um diese Bindung abzuschließen, sollten Sie dem Projekt die native Bibliothek hinzufügen. Dazu fügen Sie die native Bibliothek ihrem Projekt hinzu, indem Sie entweder die native Bibliothek aus dem Finder auf das Projekt im Projektmappen-Explorer ziehen und ablegen, oder indem Sie mit der rechten Maustaste auf das Projekt klicken undDateien hinzufügen> auswählen, um die native Bibliothek auszuwählen. Native Bibliotheken nach Konvention beginnen mit dem Wort "lib" und enden mit der Erweiterung ".a". Wenn Sie dies tun, fügt Visual Studio für Mac zwei Dateien hinzu: die A-Datei und eine automatisch aufgefüllte C#-Datei, die Informationen darüber enthält, was die native Bibliothek enthält:

Native Bibliotheken nach Konvention beginnen mit dem Wort lib und enden mit der Erweiterung .a

Der Inhalt der libMagicChord.linkwith.cs Datei enthält Informationen dazu, wie diese Bibliothek verwendet werden kann, und weist Ihre IDE an, diese Binärdatei in die resultierende DLL-Datei zu packen:

using System;
using ObjCRuntime;

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

Vollständige Details zur Verwendung der[LinkWith] -Attribut sind im Referenzhandbuch für Bindungstypen dokumentiert.

Wenn Sie nun das Projekt erstellen, erhalten Sie eine MagicChords.dll Datei, die sowohl die Bindung als auch die native Bibliothek enthält. Sie können dieses Projekt oder die resultierende DLL zur eigenen Verwendung an andere Entwickler verteilen.

Manchmal können Sie feststellen, dass Sie einige Enumerationswerte, Delegierungsdefinitionen oder andere Typen benötigen. Platzieren Sie diese nicht in der API-Definitionsdatei, da es sich lediglich um einen Vertrag handelt.

Die API-Definitionsdatei

Die API-Definitionsdatei besteht aus einer Reihe von Schnittstellen. Die Schnittstellen in der API-Definition werden in eine Klassendeklaration umgewandelt und müssen mit dem [BaseType] -Attribut versehen werden, um die Basisklasse für die Klasse anzugeben.

Sie fragen sich vielleicht, warum wir keine Klassen anstelle von Schnittstellen für die Vertragsdefinition verwendet haben. Wir haben Schnittstellen ausgewählt, da wir den Vertrag für eine Methode schreiben konnten, ohne einen Methodentext in der API-Definitionsdatei angeben zu müssen oder einen Text angeben zu müssen, der eine Ausnahme auslösen oder einen aussagekräftigen Wert zurückgeben musste.

Da wir jedoch die Schnittstelle als Skelett zum Generieren einer Klasse verwenden, mussten wir verschiedene Teile des Vertrags mit Attributen dekorieren, um die Bindung zu steuern.

Bindungsmethoden

Die einfachste Bindung, die Sie tun können, besteht darin, eine Methode zu binden. Deklarieren Sie einfach eine Methode in der Schnittstelle mit den C#-Benennungskonventionen, und dekorieren Sie die Methode mit dem[Export] -Attribut. Das [Export] -Attribut verknüpft Ihren C#-Namen mit dem Objective-C Namen in der Xamarin.iOS-Runtime. Der Parameter des[Export] -Attribut ist der Name des Objective-C Selektors. Einige Beispiele:

// 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);

Die obigen Beispiele zeigen, wie Sie instance-Methoden binden können. Um statische Methoden zu binden, müssen Sie das [Static] -Attribut wie folgt verwenden:

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

Dies ist erforderlich, da der Vertrag Teil einer Schnittstelle ist und Schnittstellen keine Vorstellung von statischen und instance Deklarationen haben, sodass es erneut erforderlich ist, auf Attribute zurückzugreifen. Wenn Sie eine bestimmte Methode vor der Bindung ausblenden möchten, können Sie die -Methode mit dem [Internal] -Attribut dekorieren.

Der btouch-native Befehl führt Überprüfungen ein, ob Verweisparameter nicht NULL sind. Wenn Sie NULL-Werte für einen bestimmten Parameter zulassen möchten, verwenden Sie die[NullAllowed] -Attribut für den Parameter, wie folgt:

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

Beim Exportieren eines Verweistyps können Sie mit dem [Export] Schlüsselwort (keyword) auch die Zuordnungssemantik angeben. Dies ist erforderlich, um sicherzustellen, dass keine Daten geleakt werden.

Bindungseigenschaften

Genau wie Methoden Objective-C werden Eigenschaften mit dem[Export] attributieren und C#-Eigenschaften direkt zugeordnet werden. Genau wie Methoden können Eigenschaften mit dem[Static]und den [Internal] Attribute.

Wenn Sie das [Export] -Attribut für eine Eigenschaft unter dem Cover verwenden, bindet btouch-native tatsächlich zwei Methoden: den Getter und den Setter. Der Name, den Sie zum Exportieren angeben, ist der Basisname , und der Setter wird berechnet, indem das Wort "set" vorausgestellt wird, der erste Buchstabe des Basisnamens in Großbuchstaben umgewandelt wird und der Selektor ein Argument annimmt. Dies bedeutet, dass auf [Export ("label")] eine Eigenschaft angewendet tatsächlich die Methoden "label" und "setLabel:" Objective-C gebunden werden.

Manchmal folgen die Objective-C Eigenschaften nicht dem oben beschriebenen Muster, und der Name wird manuell überschrieben. In diesen Fällen können Sie die Art und Weise steuern, wie die Bindung generiert wird, indem Sie die[Bind] -Attribut für den Getter oder Setter, z. B.:

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

Dadurch werden dann "isMenuVisible" und "setMenuVisible:" gebunden. Optional kann eine Eigenschaft mithilfe der folgenden Syntax gebunden werden:

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

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

Wobei getter und setter explizit als in den name oben genannten Bindungen und setName definiert sind.

Zusätzlich zur Unterstützung für statische Eigenschaften mit [Static]können Sie threadstatige Eigenschaften mit [IsThreadStatic]versehen, z. B.:

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

Genau wie Methoden ermöglichen, dass einige Parameter mit [NullAllowed]gekennzeichnet werden, können Sie anwenden.[NullAllowed] an eine -Eigenschaft, um anzugeben, dass NULL ein gültiger Wert für die Eigenschaft ist, z. B.:

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

Der [NullAllowed] Parameter kann auch direkt im Setter angegeben werden:

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

Einschränkungen beim Binden benutzerdefinierter Steuerelemente

Die folgenden Einschränkungen sollten beim Einrichten der Bindung für ein benutzerdefiniertes Steuerelement berücksichtigt werden:

  1. Bindungseigenschaften müssen statisch sein : Beim Definieren der Bindung von Eigenschaften muss das [Static] -Attribut verwendet werden.
  2. Eigenschaftennamen müssen genau übereinstimmen : Der Name, der zum Binden der Eigenschaft verwendet wird, muss genau mit dem Namen der Eigenschaft im benutzerdefinierten Steuerelement übereinstimmen.
  3. Eigenschaftentypen müssen genau übereinstimmen : Der Variablentyp, der zum Binden der Eigenschaft verwendet wird, muss genau mit dem Typ der Eigenschaft im benutzerdefinierten Steuerelement übereinstimmen.
  4. Haltepunkte und der Getter/Setter : Haltepunkte, die in den Getter- oder Settermethoden der -Eigenschaft platziert werden, werden nie erreicht.
  5. Rückrufe beobachten : Sie müssen Beobachtungsrückrufe verwenden, um über Änderungen in den Eigenschaftswerten benutzerdefinierter Steuerelemente benachrichtigt zu werden.

Wenn sie keine der oben aufgeführten Einschränkungen beachten, kann dies dazu führen, dass die Bindung zur Laufzeit automatisch fehlschlägt.

Objective-C veränderbare Muster und Eigenschaften

Objective-C -Frameworks verwenden eine Idiom, bei der einige Klassen mit einer veränderlichen Unterklasse unveränderlich sind. Beispielsweise NSString ist die unveränderliche Version, während NSMutableString die Unterklasse ist, die Mutation zulässt.

In diesen Klassen ist es üblich, dass die unveränderliche Basisklasse Eigenschaften mit einem Getter, aber keinen Setter enthält. Und für die veränderliche Version, um den Setter einzuführen. Da dies mit C# nicht wirklich möglich ist, mussten wir dieses Idiom in eine Idiom zuordnen, die mit C# funktionieren würde.

Dies wird C# zugeordnet, indem sie sowohl den Getter als auch den Setter für die Basisklasse hinzufügen, den Setter jedoch mit einem kennzeichnen.[NotImplemented] -Attribut.

Verwenden Sie dann für die veränderliche Unterklasse die[Override] -Attribut für die -Eigenschaft, um sicherzustellen, dass die Eigenschaft tatsächlich das Verhalten des übergeordneten Elements überschreibt.

Beispiel:

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

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

Bindungskonstruktoren

Das btouch-native Tool generiert automatisch vier Konstruktoren in Ihrer Klasse. Für eine bestimmte Klasse Foogeneriert es:

  • Foo (): Der Standardkonstruktor (ordnet dem "init"-Konstruktor zu Objective-C)
  • Foo (NSCoder): Der Konstruktor, der während der Deserialisierung von NIB-Dateien verwendet wird (wird dem Objective-CKonstruktor "initWithCoder:" zugeordnet).
  • Foo (IntPtr handle): Der Konstruktor für die handle-basierte Erstellung, wird von der Runtime aufgerufen, wenn die Runtime ein verwaltetes Objekt aus einem nicht verwalteten Objekt verfügbar machen muss.
  • Foo (NSEmptyFlag): Dies wird von abgeleiteten Klassen verwendet, um eine doppelte Initialisierung zu verhindern.

Für Konstruktoren, die Sie definieren, müssen sie mit der folgenden Signatur innerhalb der Schnittstellendefinition deklariert werden: Sie müssen einen IntPtr Wert zurückgeben, und der Name der Methode sollte Konstruktor lauten. Um beispielsweise den initWithFrame: Konstruktor zu binden, würden Sie folgendes verwenden:

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

Bindungsprotokolle

Wie im API-Entwurfsdokument beschrieben, ordnet Xamarin.iOS im Abschnitt mit Modellen und Protokollen die Protokolle klassen zu, die Objective-C mit gekennzeichnet wurden.[Model] -Attribut. Dies wird in der Regel verwendet, wenn Delegatklassen implementiert Objective-C werden.

Der große Unterschied zwischen einer regulären gebundenen Klasse und einer Delegatenklasse besteht darin, dass die Delegatenklasse über eine oder mehrere optionale Methoden verfügt.

Betrachten Sie beispielsweise die UIKit Klasse UIAccelerometerDelegate, die in Xamarin.iOS so gebunden ist:

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

Da dies eine optionale Methode für die Definition ist, UIAccelerometerDelegate gibt es nichts anderes zu tun. Wenn jedoch eine erforderliche Methode für das Protokoll vorhanden ist, sollten Sie die[Abstract] -Attribut für die -Methode. Dadurch wird erzwungen, dass der Benutzer der -Implementierung tatsächlich einen Text für die -Methode bereitstellt.

Im Allgemeinen werden Protokolle in Klassen verwendet, die auf Nachrichten reagieren. Dies erfolgt Objective-C in der Regel, indem der Eigenschaft "delegate" ein instance eines Objekts zugewiesen wird, das auf die Methoden im Protokoll antwortet.

Die Konvention in Xamarin.iOS besteht darin, sowohl den Objective-C lose gekoppelten Stil zu unterstützen, bei dem dem Delegaten beliebige instance eines NSObject zugewiesen werden können, als auch eine stark typisierte Version davon verfügbar zu machen. Aus diesem Grund stellen wir in der Regel sowohl eine Delegate Eigenschaft bereit, die stark typisiert ist, als auch eine WeakDelegate eigenschaft, die lose typisiert ist. Normalerweise binden wir die lose typisierte Version an [Export], und wir verwenden das [Wrap] -Attribut, um die stark typisierte Version bereitzustellen.

Dies zeigt, wie wir die UIAccelerometer -Klasse gebunden haben:

[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; }
}

Neu in MonoTouch 7.0

Ab MonoTouch 7.0 wurde eine neue und verbesserte Protokollbindungsfunktion integriert. Diese neue Unterstützung vereinfacht die Verwendung von Objective-C Redewendungen für die Übernahme eines oder mehrerer Protokolle in einer bestimmten Klasse.

Für jede Protokolldefinition MyProtocol in Objective-Cgibt es jetzt eine IMyProtocol Schnittstelle, die alle erforderlichen Methoden aus dem Protokoll auflistet, sowie eine Erweiterungsklasse, die alle optionalen Methoden bereitstellt. In Kombination mit der neuen Unterstützung im Xamarin Studio-Editor können Entwickler Protokollmethoden implementieren, ohne die separaten Unterklassen der vorherigen abstrakten Modellklassen verwenden zu müssen.

Jede Definition, die das [Protocol] Attribut enthält, generiert tatsächlich drei unterstützende Klassen, die die Art der Nutzung von Protokollen erheblich verbessern:

// 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);
    }
}

Die Klassenimplementierung stellt eine vollständige abstrakte Klasse bereit, mit der Sie einzelne Methoden von überschreiben und vollständige Typsicherheit erhalten können. Da C# jedoch keine mehrfache Vererbung unterstützt, gibt es Szenarien, in denen Sie möglicherweise eine andere Basisklasse benötigen, aber dennoch eine Schnittstelle implementieren möchten.

Die generierte Schnittstellendefinition wird eingefügt. Es handelt sich um eine Schnittstelle, die über alle erforderlichen Methoden aus dem Protokoll verfügt. Dadurch können Entwickler, die Ihr Protokoll implementieren möchten, lediglich die Schnittstelle implementieren. Die Runtime registriert den Typ automatisch als Übernahme des Protokolls.

Beachten Sie, dass die -Schnittstelle nur die erforderlichen Methoden auflistet und die optionalen Methoden verfügbar macht. Dies bedeutet, dass Klassen, die das Protokoll übernehmen, eine vollständige Typüberprüfung für die erforderlichen Methoden erhalten, aber auf schwache Eingaben (manuelle Verwendung von [Export] Attributen und Abgleich der Signatur) für die optionalen Protokollmethoden zurückgreifen müssen.

Um die Verwendung einer API zu vereinfachen, die Protokolle verwendet, erstellt das Bindungstool auch eine Erweiterungsmethodeklasse, die alle optionalen Methoden verfügbar macht. Dies bedeutet, dass Sie, solange Sie eine API verwenden, Protokolle als mit allen Methoden behandeln können.

Wenn Sie die Protokolldefinitionen in Ihrer API verwenden möchten, müssen Sie gerüstleere Schnittstellen in Ihre API-Definition schreiben. Wenn Sie myProtocol in einer API verwenden möchten, müssen Sie folgendes tun:

[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 ();
}

Das oben genannte wird benötigt, da zum Zeitpunkt der Bindung nicht IMyProtocol vorhanden wäre, weshalb Sie eine leere Schnittstelle bereitstellen müssen.

Übernehmen von protokollgenerierten Schnittstellen

Immer wenn Sie eine der Schnittstellen implementieren, die für die Protokolle generiert werden, wie folgt:

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

Die Implementierung für die erforderlichen Schnittstellenmethoden wird mit dem richtigen Namen exportiert, sodass sie dem folgenden entspricht:

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

Dies funktioniert für alle erforderlichen Protokollmember, aber es gibt einen Sonderfall mit optionalen Selektoren, die beachtet werden müssen. Optionale Protokollmember werden bei Verwendung der Basisklasse identisch behandelt:

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

Bei Verwendung der Protokollschnittstelle muss jedoch [Export] hinzugefügt werden. Die IDE fügt sie über autovervollständigen hinzu, wenn Sie sie ab dem Überschreiben hinzufügen.

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

Es gibt einen geringfügigen Verhaltensunterschied zwischen den beiden zur Laufzeit.

  • Benutzer der Basisklasse (NSUrlSessionDownloadDelegate im Beispiel) stellen alle erforderlichen und optionalen Selektoren bereit und geben vernünftige Standardwerte zurück.
  • Benutzer der Schnittstelle (beispiel: INSUrlSessionDownloadDelegate) reagieren nur auf die genau angegebenen Selektoren.

Einige seltene Klassen können sich hier anders verhalten. In fast allen Fällen ist es jedoch sicher, beides zu verwenden.

Bindungsklassenerweiterungen

In Objective-C ist es möglich, Klassen mit neuen Methoden zu erweitern, ähnlich wie die Erweiterungsmethoden von C#. Wenn eine dieser Methoden vorhanden ist, können Sie die[BaseType] -Attribut, um die -Methode als Empfänger der Objective-C Nachricht zu kennzeichnen.

In Xamarin.iOS haben wir beispielsweise die Erweiterungsmethoden gebunden, die für NSString definiert sind, wenn UIKit als Methoden in importiert NSStringDrawingExtensionswerden, wie folgt:

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

Binden von Objective-C Argumentlisten

Objective-C unterstützt variadische Argumente. Beispiel:

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

Um diese Methode aus C# aufzurufen, sollten Sie eine Signatur wie folgt erstellen:

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

Dadurch wird die Methode als intern deklariert, wobei die oben genannte API für Benutzer ausgeblendet wird, aber für die Bibliothek verfügbar ist. Anschließend können Sie eine Methode wie die folgende schreiben:

public void AppendWorkers(params Worker[] workers)
{
    if (workers == 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);
}

Bindungsfelder

Manchmal möchten Sie auf öffentliche Felder zugreifen, die in einer Bibliothek deklariert wurden.

In der Regel enthalten diese Felder Zeichenfolgen oder ganzzahlige Werte, auf die verwiesen werden muss. Sie werden häufig als Zeichenfolge verwendet, die eine bestimmte Benachrichtigung darstellen, und als Schlüssel in Wörterbüchern.

Um ein Feld zu binden, fügen Sie ihrer Schnittstellendefinitionsdatei eine Eigenschaft hinzu, und versehen Sie die Eigenschaft mit dem [Field] -Attribut. Dieses Attribut verwendet einen Parameter: den C-Namen des nachschlagenden Symbols. Beispiel:

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

Wenn Sie verschiedene Felder in einer statischen Klasse umschließen möchten, die nicht von NSObjectabgeleitet ist, können Sie die[Static] -Attribut für die -Klasse, wie folgt:

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

Mit dem obigen Code wird eine LonelyClass generiert, die nicht von NSObject abgeleitet ist und eine Bindung an die enthält, die NSSomeEventNotificationNSString als NSStringverfügbar gemacht wird.

Das [Field] Attribut kann auf die folgenden Datentypen angewendet werden:

  • NSString Verweise (nur schreibgeschützte Eigenschaften)
  • NSArray Verweise (nur schreibgeschützte Eigenschaften)
  • 32-Bit-Ints (System.Int32)
  • 64-Bit-Ints (System.Int64)
  • 32-Bit-Floats (System.Single)
  • 64-Bit-Floats (System.Double)
  • System.Drawing.SizeF
  • CGSize

Zusätzlich zum systemeigenen Feldnamen können Sie den Bibliotheksnamen angeben, in dem sich das Feld befindet, indem Sie den Bibliotheksnamen übergeben:

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

Wenn Sie statisch verknüpfen, gibt es keine Bibliothek, mit der eine Bindung hergestellt werden kann, daher müssen Sie den __Internal Namen verwenden:

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

Bindungsenumen

Sie können Ihren Bindungsdateien direkt hinzufügen enum , um die Verwendung innerhalb von API-Definitionen zu vereinfachen, ohne eine andere Quelldatei zu verwenden (die sowohl in den Bindungen als auch im endgültigen Projekt kompiliert werden muss).

Beispiel:

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

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

Es ist auch möglich, eigene Enumerationen zu erstellen, um Konstanten zu ersetzen NSString . In diesem Fall erstellt der Generator automatisch die Methoden zum Konvertieren von Enumerationswerten und NSString-Konstanten für Sie.

Beispiel:

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);
}

Im obigen Beispiel können Sie sich entscheiden, mit einem [Internal] -Attribut zu versehenvoid Perform (NSString mode);. Dadurch wird die konstantenbasierte API vor Ihren Bindungsconsumern ausgeblendet .

Dies würde jedoch die Unterklassifizierung des Typs einschränken, da die nette api-Alternative ein [Wrap] -Attribut verwendet. Diese generierten Methoden sind nicht virtual, d. h. Sie können sie nicht überschreiben . Dies kann eine gute Wahl sein oder nicht.

Eine Alternative besteht darin, die ursprüngliche , NSString-basierte Definition als [Protected]zu markieren. Dadurch kann bei Bedarf eine Unterklasse ausgeführt werden, und die Umbruchversion funktioniert weiterhin und ruft die überschriebene Methode auf.

Binden von NSValue, NSNumberund NSString an einen besseren Typ

Das [BindAs] Attribut ermöglicht das Binden von NSNumber, NSValue und NSString(Enumerationen) in genauere C#-Typen. Das Attribut kann verwendet werden, um eine bessere und genauere .NET-API gegenüber der nativen API zu erstellen.

Sie können Methoden (auf Rückgabewert), Parameter und Eigenschaften mit [BindAs]ergänzen. Die einzige Einschränkung besteht darin, dass sich Ihr Mitglied nicht in einem[Protocol] oder [Model] Schnittstelle.

Beispiel:

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

Ausgabe:

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

Intern werden wir die bool?<-NSNumber> und<CGRect ->NSValue Konvertierungen durchführen.

[BindAs] unterstützt auch Arrays von NSNumberNSValue und NSString(Enumerationen).

Beispiel:

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

Ausgabe:

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

CAScroll ist eine NSString gesicherte Enumeration, wir rufen den richtigen NSString Wert ab und behandeln die Typkonvertierung.

Die unterstützten Konvertierungstypen finden Sie in der [BindAs] Dokumentation.

Bindungsbenachrichtigungen

Benachrichtigungen sind Nachrichten, die an die NSNotificationCenter.DefaultCenter gesendet werden und als Mechanismus zum Übertragen von Nachrichten von einem Teil der Anwendung in einen anderen verwendet werden. Entwickler abonnieren Benachrichtigungen in der Regel mithilfe der AddObserver-Methode von NSNotificationCenter. Wenn eine Anwendung eine Nachricht an das Notification Center sendet, enthält sie in der Regel eine Nutzlast, die im NSNotification.UserInfo-Wörterbuch gespeichert ist. Dieses Wörterbuch ist schwach typisiert, und das Abrufen von Informationen ist fehleranfällig, da Benutzer in der Regel in der Dokumentation lesen müssen, welche Schlüssel im Wörterbuch verfügbar sind und welche Typen der Werte im Wörterbuch gespeichert werden können. Das Vorhandensein von Schlüsseln wird manchmal auch als boolescher Wert verwendet.

Der Xamarin.iOS-Bindungsgenerator unterstützt Entwickler beim Binden von Benachrichtigungen. Zu diesem Ziel legen Sie die[Notification] -Attribut für eine Eigenschaft, die ebenfalls mit einem gekennzeichnet wurde[Field] -Eigenschaft (kann öffentlich oder privat sein).

Dieses Attribut kann ohne Argumente für Benachrichtigungen verwendet werden, die keine Nutzlast enthalten, oder Sie können eine angeben, die auf eine System.Type andere Schnittstelle in der API-Definition verweist, in der Regel mit dem Namen, der auf "EventArgs" endet. Der Generator wandelt die Schnittstelle in eine Klasse um, die Unterklassen EventArgs enthält und alle dort aufgeführten Eigenschaften enthält. Das [Export] -Attribut sollte in der EventArgs-Klasse verwendet werden, um den Namen des Schlüssels aufzulisten, der zum Suchen des Objective-C Wörterbuchs zum Abrufen des Werts verwendet wird.

Beispiel:

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

Der obige Code generiert eine geschachtelte Klasse MyClass.Notifications mit den folgenden Methoden:

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

Benutzer Ihres Codes können dann problemlos Benachrichtigungen abonnieren, die im NSDefaultCenter bereitgestellt werden, indem sie Code wie den folgenden verwenden:

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

Der zurückgegebene Wert von ObserveDidStart kann wie folgt verwendet werden, um den Empfang von Benachrichtigungen problemlos zu beenden:

token.Dispose ();

Alternativ können Sie NSNotification.DefaultCenter.RemoveObserver aufrufen und das Token übergeben. Wenn Ihre Benachrichtigung Parameter enthält, sollten Sie eine Hilfsschnittstelle EventArgs wie folgt angeben:

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; }
}

Mit dem obigen Code wird eine MyScreenChangedEventArgs Klasse mit den ScreenX Eigenschaften und ScreenY generiert, die die Daten aus dem NSNotification.UserInfo-Wörterbuch mit den Schlüsseln "ScreenXKey" bzw. "ScreenYKey" abruft und die richtigen Konvertierungen anwendet. Das [ProbePresence] -Attribut wird für den Generator verwendet, um zu überprüfen, ob der Schlüssel in UserInfofestgelegt ist, anstatt zu versuchen, den Wert zu extrahieren. Dies wird für Fälle verwendet, in denen das Vorhandensein des Schlüssels der Wert ist (in der Regel für boolesche Werte).

Dadurch können Sie Code wie folgt schreiben:

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

Bindungskategorien

Kategorien sind ein Objective-C Mechanismus, der verwendet wird, um den Satz von Methoden und Eigenschaften zu erweitern, die in einer Klasse verfügbar sind. In der Praxis werden sie verwendet, um entweder die Funktionalität einer Basisklasse zu erweitern (z. B NSObject. ), wenn ein bestimmtes Framework in verknüpft ist (z. B UIKit. ), um deren Methoden verfügbar zu machen, aber nur, wenn das neue Framework verknüpft ist. In einigen anderen Fällen werden sie verwendet, um Features in einer Klasse nach Funktionalität zu organisieren. Sie ähneln im Geiste C#-Erweiterungsmethoden. So würde eine Kategorie in Objective-Caussehen:

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

Das obige Beispiel, wenn in einer Bibliothek gefunden wird, würde Instanzen von UIView mit der -Methode makeBackgroundRederweitern.

Um diese zu binden, können Sie das [Category] -Attribut für eine Schnittstellendefinition verwenden. Wenn Sie den[Category] -Attribut, die Bedeutung von[BaseType] Attributänderungen, die verwendet werden, um die zu erweiternde Basisklasse anzugeben, um den zu erweiternden Typ zu sein.

Im Folgenden wird gezeigt, wie die UIView Erweiterungen gebunden und in C#-Erweiterungsmethoden umgewandelt werden:

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

Mit dem obigen Code wird eine MyUIViewExtension -Klasse erstellt, die die MakeBackgroundRed Erweiterungsmethode enthält. Dies bedeutet, dass Sie jetzt "MakeBackgroundRed" für jede UIView Unterklasse aufrufen können, sodass Sie die gleiche Funktionalität erhalten, die Sie auf Objective-Cerhalten würden. In einigen anderen Fällen werden Kategorien nicht verwendet, um eine Systemklasse zu erweitern, sondern um die Funktionalität zu organisieren, nur zu Dekorationszwecken. Dies sieht folgendermaßen aus:

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

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

Sie können zwar die[Category] -Attribut auch für diesen Dekorationsstil von Deklarationen können Sie ebenso gut alle einfach der Klassendefinition hinzufügen. Beides würde das gleiche erreichen:

[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);
}

In diesen Fällen ist es nur kürzer, die Kategorien zusammenzuführen:

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

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

Bindungsblöcke

Blöcke sind ein neues Konstrukt, das von Apple eingeführt wurde, um das funktionale Äquivalent von anonymen C#-Methoden in zu bringen Objective-C. Beispielsweise macht die NSSet -Klasse jetzt diese Methode verfügbar:

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

Die obige Beschreibung deklariert eine Methode namens enumerateObjectsUsingBlock: , die ein Argument namens blockakzeptiert. Dieser Block ähnelt einer anonymen C#-Methode, da er Unterstützung für die Erfassung der aktuellen Umgebung hat (der Zeiger "this", Zugriff auf lokale Variablen und Parameter). Mit der obigen Methode in NSSet wird der Block mit zwei Parametern aufgerufen NSObject : (der id obj Teil) und einem Zeiger auf einen booleschen Teil (der BOOL *stop- Teil).

Um diese Art von API mit btouch zu binden, müssen Sie zuerst die Blocktypsignatur als C#-Delegaten deklarieren und dann wie folgt von einem API-Einstiegspunkt aus darauf verweisen:

// 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)

Und jetzt kann Ihr Code Ihre Funktion aus C# aufrufen:

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);
});

Sie können auch Lambdas verwenden, wenn Sie folgendes bevorzugen:

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);
});

Asynchrone Methoden

Der Bindungsgenerator kann eine bestimmte Klasse von Methoden in asynchrone, freundliche Methoden umwandeln (Methoden, die einen Task oder Task<T> zurückgeben).

Sie können das[Async] -Attribut für Methoden, die void zurückgeben und deren letztes Argument ein Rückruf ist. Wenn Sie dies auf eine Methode anwenden, generiert der Bindungsgenerator eine Version dieser Methode mit dem Suffix Async. Wenn der Rückruf keine Parameter akzeptiert, ist der Rückgabewert ein Task, wenn der Rückruf einen -Parameter akzeptiert, ist das Ergebnis ein Task<T>. Wenn der Rückruf mehrere Parameter akzeptiert, sollten Sie oder ResultTypeResultTypeName festlegen, um den gewünschten Namen des generierten Typs anzugeben, der alle Eigenschaften enthält.

Beispiel:

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

Der obige Code generiert sowohl die LoadFile-Methode als auch Folgendes:

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

Starke Typen für schwache NSDictionary-Parameter

An vielen Stellen in der Objective-C API werden Parameter als schwach typisierte NSDictionary APIs mit bestimmten Schlüsseln und Werten übergeben, aber diese sind fehleranfällig (Sie können ungültige Schlüssel übergeben und erhalten keine Warnungen; Sie können ungültige Werte übergeben und erhalten keine Warnungen) und frustrierend, da sie mehrere Fahrten zur Dokumentation erfordern, um die möglichen Schlüsselnamen und Werte zu suchen.

Die Lösung besteht darin, eine stark typisierte Version bereitzustellen, die die stark typisierte Version der API bereitstellt und hinter den Kulissen die verschiedenen zugrunde liegenden Schlüssel und Werte ordnet.

Wenn die Objective-C API also beispielsweise einen NSDictionary akzeptiert hat und dokumentiert ist, dass der Schlüssel XyzVolumeKey verwendet wird, der einen NSNumber mit einem Volumewert von 0,0 bis 1,0 nimmt, und eine XyzCaptionKey , die eine Zeichenfolge akzeptiert, möchten Sie, dass Ihre Benutzer eine schöne API haben, die wie folgt aussieht:

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

Die Volume Eigenschaft ist als nullable float definiert, da die Konvention in Objective-C nicht erfordert, dass diese Wörterbücher über den Wert verfügen, sodass es Szenarien gibt, in denen der Wert möglicherweise nicht festgelegt wird.

Dazu müssen Sie einige Schritte ausführen:

  • Erstellen Sie eine stark typisierte Klasse, die DictionaryContainer unterklassiert und die verschiedenen Getter und Setter für jede Eigenschaft bereitstellt.
  • Deklarieren Sie Überladungen für die Methoden, NSDictionary die die neue stark typisierte Version verwenden.

Sie können die stark typisierte Klasse entweder manuell erstellen oder den Generator verwenden, um die Arbeit für Sie zu erledigen. Wir untersuchen zunächst, wie dies manuell zu tun ist, damit Sie verstehen, was passiert, und dann den automatischen Ansatz.

Hierfür müssen Sie eine unterstützende Datei erstellen, die nicht in Ihre Vertrags-API eingeht. Dies müssen Sie schreiben, um Ihre XyzOptions-Klasse zu erstellen:

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
}

Anschließend sollten Sie eine Wrappermethode bereitstellen, die die allgemeine API zusätzlich zur API auf niedriger Ebene darstellt.

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

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

Wenn Ihre API nicht überschrieben werden muss, können Sie die NSDictionary-basierte API mit dem[Internal] -Attribut.

Wie Sie sehen können, verwenden wir die[Wrap] attribut to surface a new API entry point, and we surface it using our strongly typed XyzOptions class. Die Wrappermethode ermöglicht auch das Übergeben von NULL.

Eine Sache, die wir nicht Erwähnung haben, ist, wo die XyzOptionsKeys Werte herkamen. Sie würden die Schlüssel, die von einer API in einer statischen Klasse angezeigt werden, in der Regel wie XyzOptionsKeysfolgt gruppieren:

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

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

Sehen wir uns die automatische Unterstützung für die Erstellung dieser stark typisierten Wörterbücher an. Dadurch werden viele Kessel vermieden, und Sie können das Wörterbuch direkt in Ihrem API-Vertrag definieren, anstatt eine externe Datei zu verwenden.

Um ein stark typisiertes Wörterbuch zu erstellen, führen Sie eine Schnittstelle in Ihrer API ein, und dekorieren Sie sie mit dem StrongDictionary-Attribut . Dadurch wird dem Generator mitgeteilt, dass er eine Klasse mit demselben Namen wie Ihre Schnittstelle erstellen soll, die von DictionaryContainer abgeleitet wird und starke typisierte Accessoren für sie bereitstellt.

Das [StrongDictionary] Attribut nimmt einen Parameter an, nämlich den Namen der statischen Klasse, die Ihre Wörterbuchschlüssel enthält. Anschließend wird jede Eigenschaft der Schnittstelle zu einem stark typisierten Accessor. Standardmäßig verwendet der Code den Namen der Eigenschaft mit dem Suffix "Key" in der statischen Klasse, um den Accessor zu erstellen.

Dies bedeutet, dass zum Erstellen ihres stark typisierten Accessors keine externe Datei mehr erforderlich ist, weder getter und Setter für jede Eigenschaft manuell erstellt werden müssen, noch die Schlüssel manuell selbst suchen müssen.

So würde Ihre gesamte Bindung aussehen:

[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 == null ? null : options.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

Falls Sie in Ihren XyzOption Membern auf ein anderes Feld verweisen müssen (das ist nicht der Name der Eigenschaft mit dem Suffix Key), können Sie die Eigenschaft mit einem[Export] -Attribut mit dem Namen, den Sie verwenden möchten.

Typzuordnungen

In diesem Abschnitt wird beschrieben, wie Objective-C Typen C#-Typen zugeordnet werden.

Einfache Typen

Die folgende Tabelle zeigt, wie Sie Typen aus der Objective-C CocoaTouch-Welt der Xamarin.iOS-Welt zuordnen sollten:

Objective-C Typname Einheitlicher Xamarin.iOS-API-Typ
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString (mehr zum Binden von NSString) string
char * string (siehe auch: [PlainString])
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
CoreFoundation-Typen (CF*) CoreFoundation.CF*
GLint nint
GLfloat nfloat
Foundation-Typen (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

Arrays

Die Xamarin.iOS-Runtime kümmert sich automatisch um die Konvertierung von C#-Arrays NSArrays in und die Konvertierung zurück, so z. B. die imaginäre Objective-C Methode, die eine NSArray von UIViewszurückgibt:

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

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

Ist wie folgt gebunden:

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

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

Die Idee besteht darin, ein stark typisiertes C#-Array zu verwenden, da die IDE dadurch eine ordnungsgemäße Codevervollständigung mit dem tatsächlichen Typ bereitstellen kann, ohne den Benutzer zu erraten oder die Dokumentation nachzuschlagen, um den tatsächlichen Typ der im Array enthaltenen Objekte zu ermitteln.

In Fällen, in denen Sie den tatsächlich am meisten abgeleiteten Typ nicht nachverfolgen können, der im Array enthalten ist, können Sie als Rückgabewert verwenden NSObject [] .

Selektoren

Selektoren werden in der Objective-C API als Sondertyp SELangezeigt. Beim Binden eines Selektors würden Sie den Typ zuordnen ObjCRuntime.Selector. In der Regel werden Selektoren in einer API mit einem Objekt, dem Zielobjekt und einem Selektor verfügbar gemacht, der im Zielobjekt aufgerufen werden soll. Beides entspricht im Wesentlichen dem C#-Delegat: Etwas, das sowohl die aufzurufende Methode als auch das -Objekt zum Aufrufen der -Methode kapselt.

So sieht die Bindung aus:

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

Die Methode wird in der Regel in einer Anwendung verwendet:

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

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

Um die Bindung für C#-Entwickler zu vereinfachen, stellen Sie in der Regel eine -Methode bereit, die einen NSAction Parameter akzeptiert. Dadurch können C#-Delegaten und Lambdas anstelle von Target+Selectorverwendet werden. Dazu würden Sie die Methode in der SetTarget Regel ausblenden, indem Sie sie mit einem kennzeichnen[Internal] und dann würden Sie eine neue Hilfsmethode wie folgt verfügbar machen:

// 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);
     }
}

Jetzt kann Ihr Benutzercode wie folgt geschrieben werden:

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

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

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

Zeichenfolgen

Wenn Sie eine Methode binden, die ein NSStringverwendet, können Sie diese durch einen C#-Zeichenfolgentyp ersetzen, sowohl bei Rückgabetypen als auch bei Parametern.

Der einzige Fall, in dem Sie einen NSString direkt verwenden möchten, ist, wenn die Zeichenfolge als Token verwendet wird. Weitere Informationen zu Zeichenfolgen und NSStringfinden Sie im Dokument API-Entwurf für NSString .

In einigen seltenen Fällen kann eine API eine C-ähnliche Zeichenfolge (char *) anstelle einer Objective-C Zeichenfolge (NSString *) verfügbar machen. In diesen Fällen können Sie den Parameter mit dem[PlainString] -Attribut.

out/ref-Parameter

Einige APIs geben Werte in ihren Parametern zurück oder übergeben Parameter als Verweis.

In der Regel sieht die Signatur wie folgt aus:

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

Das erste Beispiel zeigt eine allgemeine Objective-C Sprache zum Zurückgeben von Fehlercodes, ein Zeiger auf einen NSError Zeiger wird übergeben, und nach der Rückgabe wird der Wert festgelegt. Die zweite Methode zeigt, wie eine Objective-C Methode ein Objekt annehmen und dessen Inhalt ändern kann. Hierbei handelt es sich um einen Pass-by-Verweis und nicht um einen reinen Ausgabewert.

Ihre Bindung würde wie folgt aussehen:

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

Arbeitsspeicherverwaltungsattribute

Wenn Sie das [Export] Attribut verwenden und Daten übergeben, die von der aufgerufenen Methode beibehalten werden, können Sie die Argumentsemantik angeben, indem Sie sie als zweiten Parameter übergeben, z. B.:

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

Die obige Option würde den Wert mit der Semantik "Beibehalten" kennzeichnen. Die verfügbaren Semantiken sind:

  • Zuweisen
  • Kopieren
  • Beibehalten

Stilrichtlinien

Verwenden von [Intern]

Sie können das[Internal] -Attribut, um eine Methode vor der öffentlichen API auszublenden. Sie können dies in Fällen tun, in denen die verfügbar gemachte API zu niedrig ist und Sie eine allgemeine Implementierung in einer separaten Datei basierend auf dieser Methode bereitstellen möchten.

Sie können dies auch verwenden, wenn sie im Bindungsgenerator auf Einschränkungen stoßen. Beispielsweise können einige erweiterte Szenarien Typen verfügbar machen, die nicht gebunden sind und Sie auf Ihre eigene Weise binden möchten, und Sie möchten diese Typen selbst auf Ihre eigene Weise umschließen.

Ereignishandler und Rückrufe

Objective-C Klassen senden in der Regel Benachrichtigungen oder fordern Informationen an, indem sie eine Nachricht für eine Delegatenklasse (Objective-C Delegaten) senden.

Dieses Modell kann, obwohl es von Xamarin.iOS vollständig unterstützt und angezeigt wird, manchmal umständlich sein. Xamarin.iOS macht das C#-Ereignismuster und ein Methodenrückrufsystem für die Klasse verfügbar, die in diesen Situationen verwendet werden kann. Dadurch kann Code wie dieser ausgeführt werden:

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

Der Bindungsgenerator kann die Menge an Eingaben reduzieren, die erforderlich sind, um das Objective-C Muster dem C#-Muster zuzuordnen.

Ab Xamarin.iOS 1.4 ist es auch möglich, den Generator anzuweisen, Bindungen für einen bestimmten Objective-C Delegaten zu erstellen und den Delegaten als C#-Ereignisse und -Eigenschaften für den Hosttyp verfügbar zu machen.

Es sind zwei Klassen an diesem Prozess beteiligt, die Hostklasse, die derzeit Ereignisse ausgibt und diese an die Delegate oder WeakDelegate und die tatsächliche Delegatenklasse sendet.

Unter Berücksichtigung des folgenden Setups:

[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);
}

Zum Umschließen der Klasse müssen Sie Folgendes ausführen:

  • Fügen Sie in Ihrer Hostklasse Ihrem [BaseType]
    Deklaration des Typs, der als Delegat fungiert, und den C#-Namen, den Sie verfügbar gemacht haben. In unserem obigen Beispiel sind typeof (MyClassDelegate) dies und WeakDelegate jeweils.
  • In Ihrer Delegatenklasse müssen Sie für jede Methode, die mehr als zwei Parameter aufweist, den Typ angeben, den Sie für die automatisch generierte EventArgs-Klasse verwenden möchten.

Der Bindungsgenerator ist nicht darauf beschränkt, nur ein einzelnes Ereignisziel umschließen zu können. Es ist möglich, dass einige Objective-C Klassen Nachrichten an mehr als einen Delegaten ausgeben. Daher müssen Sie Arrays bereitstellen, um dieses Setup zu unterstützen. Die meisten Setups benötigen es nicht, aber der Generator ist bereit, diese Fälle zu unterstützen.

Der resultierende Code ist:

[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);
}

Wird EventArgs verwendet, um den Namen der EventArgs zu generierenden Klasse anzugeben. Sie sollten eine pro Signatur verwenden (in diesem Beispiel enthält die EventArgs eine With Eigenschaft vom Typ nint).

Mit den obigen Definitionen erzeugt der Generator das folgende Ereignis in der generierten MyClass:

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

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

Daher können Sie den Code jetzt wie folgt verwenden:

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

Rückrufe sind genau wie Ereignisaufrufe. Der Unterschied besteht darin, dass Rückrufe nur einen einzelnen Abonnenten haben können, anstatt mehrere potenzielle Abonnenten zu haben (z. B. mehrere Methoden können in ein Clicked Ereignis oder ein DownloadFinished Ereignis eingebunden werden).

Der Prozess ist identisch. Der einzige Unterschied besteht darin, dass der Name der EventArgs generierten Klasse nicht offen legt, sondern der EventArgs tatsächlich verwendet wird, um den resultierenden C#-Delegatnamen zu benennen.

Wenn die Methode in der Delegatenklasse einen Wert zurückgibt, ordnet der Bindungsgenerator diesen einer Delegatenmethode in der übergeordneten Klasse anstelle eines Ereignisses zu. In diesen Fällen müssen Sie den Standardwert angeben, der von der -Methode zurückgegeben werden soll, wenn der Benutzer keine Verbindung mit dem Delegaten hat. Dazu verwenden Sie[DefaultValue] oder [DefaultValueFromArgument] Attribute.

[DefaultValue] hardcodiert einen Rückgabewert, während[DefaultValueFromArgument] wird verwendet, um anzugeben, welches Eingabeargument zurückgegeben wird.

Enumerationen und Basistypen

Sie können auch auf Enumerationen oder Basistypen verweisen, die vom btouch-Schnittstellendefinitionssystem nicht direkt unterstützt werden. Fügen Sie dazu Ihre Enumerationen und Kerntypen in eine separate Datei ein, und fügen Sie diese als Teil einer der zusätzlichen Dateien ein, die Sie für btouch bereitstellen.

Verknüpfen der Abhängigkeiten

Wenn Sie APIs binden, die nicht Teil Ihrer Anwendung sind, müssen Sie sicherstellen, dass Ihre ausführbare Datei mit diesen Bibliotheken verknüpft ist.

Sie müssen Xamarin.iOS darüber informieren, wie Sie Ihre Bibliotheken verknüpfen. Dies kann entweder geschehen, indem Sie Ihre Buildkonfiguration ändern, um den mtouch Befehl mit einigen zusätzlichen Buildargumenten aufzurufen, die angeben, wie mit den neuen Bibliotheken mithilfe der Option "-gcc_flags" verknüpft werden soll, gefolgt von einer Anführungszeichenfolge, die alle zusätzlichen Bibliotheken enthält, die für Ihr Programm erforderlich sind. So:

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

Im obigen Beispiel werden und libSystemLibrary.dylib die CFNetwork Frameworkbibliothek mit Ihrer endgültigen ausführbaren Datei verknüpftlibMyLibrary.a.

Oder Sie können die Vorteile der Assemblyebene [LinkWithAttribute]nutzen, die Sie in Ihre Vertragsdateien einbetten können (z. B AssemblyInfo.cs. ). Wenn Sie die [LinkWithAttribute]verwenden, müssen Sie Ihre native Bibliothek zum Zeitpunkt der Bindung verfügbar haben, da dadurch die native Bibliothek in Ihre Anwendung eingebettet wird. Beispiel:

// 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)]

Sie fragen sich vielleicht, warum Sie den Befehl benötigen -force_load , und der Grund ist, dass das Flag -ObjC, obwohl es den Code in kompiliert, nicht die Metadaten beibewahrt, die zum Unterstützen von Kategorien erforderlich sind (die Linker-/Compiler-Ausscheidung des unzustellbaren Codes entfernt ihn), die Sie zur Laufzeit für Xamarin.iOS benötigen.

Unterstützte Verweise

Einige vorübergehende Objekte wie Aktionsblätter und Warnungsfelder sind für Entwickler umständlich, und der Bindungsgenerator kann hier ein wenig helfen.

Wenn Sie z. B. eine Klasse hatten, die eine Nachricht angibt und dann ein Done Ereignis generiert hat, wäre dies die herkömmliche Behandlung:

class Demo {
    MessageBox box;

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

Im obigen Szenario muss der Entwickler den Verweis auf das Objekt selbst beibehalten und entweder den Verweis für box selbst löschen oder löschen. Während der Bindungscode vom Generator unterstützt, den Verweis für Sie nachzuverfolgen und ihn zu löschen, wenn eine spezielle Methode aufgerufen wird, würde der obige Code dann wie folgt sein:

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

Beachten Sie, dass es nicht mehr erforderlich ist, die Variable in einem instance beizubehalten, dass sie mit einer lokalen Variablen funktioniert und dass es nicht erforderlich ist, den Verweis zu löschen, wenn das Objekt stirbt.

Um dies zu nutzen, sollte ihre Klasse eine Events-Eigenschaft in der [BaseType] Deklaration und auch die KeepUntilRef Variable auf den Namen der Methode festgelegt haben, die aufgerufen wird, wenn das Objekt seine Arbeit abgeschlossen hat, wie folgt:

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

Protokolle erben

Ab Xamarin.iOS v3.2 unterstützen wir das Erben von Protokollen, die mit der [Model] -Eigenschaft markiert wurden. Dies ist bei bestimmten API-Mustern nützlich, z. B. in MapKit dem das MKOverlay Protokoll vom MKAnnotation Protokoll erbt, und wird von einer Reihe von Klassen übernommen, die von NSObjecterben.

In der Vergangenheit mussten wir das Protokoll in jede Implementierung kopieren, aber in diesen Fällen können wir jetzt die MKShape Klasse vom MKOverlay Protokoll erben lassen und alle erforderlichen Methoden automatisch generieren.