Freigeben über


Binden von Objective-C Bibliotheken

Beim Arbeiten mit Xamarin.iOS oder Xamarin.Mac können Fälle auftreten, in denen Sie eine Drittanbieterbibliothek Objective-C nutzen möchten. In diesen Situationen können Sie Xamarin Binding Projects verwenden, um eine C#-Bindung an die systemeigenen Objective-C Bibliotheken zu erstellen. Das Projekt verwendet dieselben Tools, mit denen wir die iOS- und Mac-APIs in C# übertragen.

In diesem Dokument wird beschrieben, wie ApIs gebunden Objective-C werden, wenn Sie nur C-APIs binden, sollten Sie hierfür den standardmäßigen .NET-Mechanismus verwenden, das P/Invoke-Framework. Details zum statischen Verknüpfen einer C-Bibliothek finden Sie auf der Seite "Native Bibliotheken verknüpfen".

Weitere Informationen finden Sie in unserem Referenzhandbuch für Bindungstypen. Wenn Sie mehr darüber erfahren möchten, was unter der Haube passiert, lesen Sie unsere Seite "Binding Overview ".

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, die iOS-Bibliotheksbindungsbibliothek>>, auswählen:

Gehen Sie dazu aus Visual Studio für Mac, indem Sie den Projekttyp

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

Hier definieren Sie den API-Vertrag. Dies ApiDefinition.cs 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 Hauptthema der Diskussion dieses Dokuments und der Inhalt dieser Datei sind auf C#-Schnittstellen und C#-Stellvertretungsdeklarationen beschränkt. Die StructsAndEnums.cs Datei ist die Datei, in der Sie alle Definitionen eingeben, die von den Schnittstellen und Delegaten benötigt werden. Dazu gehören Enumerationswerte und Strukturen, die ihr Code verwenden kann.

Binden einer API

Um eine umfassende Bindung auszuführen, sollten Sie die Objective-C API-Definition verstehen und sich mit den .NET Framework-Designrichtlinien 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 Attributen versehen wurden, die die Bindung fördern. Diese Datei definiert, was der Vertrag zwischen C# und Objective-C ist.

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 definiert Cocos2D.Camera , die vom NSObject Basistyp abgeleitet wird (dieser Typ stammt von Foundation.NSObject) und definiert eine statische Eigenschaft (ZEye), zwei Methoden, die keine Argumente verwenden, 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 unten im Abschnitt zur API-Definitionsdatei .

Um eine vollständige Bindung zu erstellen, behandeln Sie in der Regel vier Komponenten:

  • Die API-Definitionsdatei (ApiDefinition.cs in der Vorlage).
  • Optional: alle Enumerationen, Typen, Strukturen, die von der API-Definitionsdatei erforderlich sind (StructsAndEnums.cs in der Vorlage).
  • Optional: zusätzliche Quellen, die die generierte Bindung erweitern oder eine weitere C#-benutzerfreundliche API bereitstellen (alle C#-Dateien, die Sie dem Projekt hinzufügen).
  • Die systemeigene 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 Beispiel oberhalb des "CameraMode" ist ein Enumerationswert, der nicht in der CS-Datei vorhanden ist und in einer separaten Datei gehostet werden soll, z StructsAndEnums.cs. B. :

public enum CameraMode {
    FlyOver, Back, Follow
}

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

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

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

Das Erstellen der Bibliothek erzeugt Ihre systemeigene Bindung.

Um diese Bindung abzuschließen, sollten Sie dem Projekt die systemeigene Bibliothek hinzufügen. Dazu können Sie die systemeigene Bibliothek zu Ihrem Projekt hinzufügen, entweder durch Ziehen und Ablegen der nativen Bibliothek aus Finder auf das Projekt im Projektmappen-Explorer oder durch Klicken mit der rechten Maustaste auf das Projekt und Auswählen von "Dateien hinzufügen>", um die systemeigene Bibliothek auszuwählen. Native Bibliotheken nach Konvention beginnen mit dem Wort "lib" und enden mit der Erweiterung ".a". In diesem Fall fügen Visual Studio für Mac zwei Dateien hinzu: die .a-Datei und eine automatisch ausgefüllte C#-Datei, die Informationen darüber enthält, was die systemeigene Bibliothek enthält:

Native Bibliotheken nach Konvention beginnen mit der Wortbibliothek 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 verpacken:

using System;
using ObjCRuntime;

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

Vollständige Details zur Verwendung der [LinkWith] attribut are documented in the Binding Types Reference Guide.

Wenn Sie nun das Projekt erstellen, erhalten Sie eine MagicChords.dll Datei, die sowohl die Bindung als auch die systemeigene Bibliothek enthält. Sie können dieses Projekt oder die resultierende DLL für ihre eigene Verwendung an andere Entwickler verteilen.

Manchmal stellen Sie möglicherweise fest, dass Sie einige Enumerationswerte, Delegatendefinitionen oder andere Typen benötigen. Platzieren Sie diese nicht in der API-Definitionsdatei, da dies lediglich ein Vertrag ist.

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.

Vielleicht fragen Sie sich, 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 bereitstellen 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 verwenden, um eine Klasse zu generieren, mussten wir verschiedene Teile des Vertrags mit Attributen versehen, um die Bindung zu fördern.

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 versehen Sie die Methode mit der [Export] Attribut. Das [Export] Attribut verknüpft Ihren C#-Namen mit dem Objective-C Namen in der Xamarin.iOS-Laufzeit. Der Parameter des [Export] attribut is the name of the Objective-C selector. 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);

In den obigen Beispielen wird gezeigt, wie Sie Instanzmethoden 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 statischen Deklarationen im Vergleich zu Instanzen aufweisen, daher ist es erneut erforderlich, auf Attribute zurückzugreifen. Wenn Sie eine bestimmte Methode aus der Bindung ausblenden möchten, können Sie die Methode mit dem [Internal] Attribut versehen.

Mit dem btouch-native Befehl werden Überprüfungen auf Referenzparameter eingeführt, die nicht null sind. Wenn Sie Nullwerte 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 auch die Zuordnungssemantik angeben. Dies ist erforderlich, um sicherzustellen, dass keine Daten verloren gehen.

Bindungseigenschaften

Genau wie Methoden Objective-C werden Eigenschaften mithilfe der [Export] Attribut und direkte Zuordnung zu C#-Eigenschaften. Genau wie Methoden können Eigenschaften mit dem [Static] Und die [Internal] Attribute.

Wenn Sie das [Export] Attribut für eine Eigenschaft unter den Covern 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" voraussteht, wobei der erste Buchstabe des Basisnamens in Großbuchstaben umgewandelt und die Auswahl ein Argument verwendet. Dies bedeutet, dass [Export ("label")] eine Eigenschaft tatsächlich die Methoden "label" und "setLabel:" Objective-C bindet.

Manchmal folgen die Objective-C Eigenschaften nicht dem oben beschriebenen Muster, und der Name wird manuell überschrieben. In diesen Fällen können Sie steuern, wie die Bindung mithilfe der [Bind] attribut on the getter or setter, for example:

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

Dadurch werden "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);
}

Wo der Getter und setter explizit wie in den name setName obigen Bindungen definiert sind.

Zusätzlich zur Unterstützung statischer Eigenschaften mithilfe [Static]von statischen Eigenschaften können Sie threadstatische Eigenschaften mit [IsThreadStatic]z. B. versehen:

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

Genau wie Methoden erlauben, einige Parameter mit [NullAllowed]gekennzeichnet zu 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 auf dem Setter angegeben werden:

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

Einschränkungen bei der Bindung von benutzerdefinierten Steuerelementen

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 exakt übereinstimmen – Der Name, der zum Binden der Eigenschaft verwendet wird, muss exakt mit dem Namen der Eigenschaft im benutzerdefinierten Steuerelement übereinstimmen.
  3. Eigenschaftentypen müssen exakt übereinstimmen – Der variable Typ, 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 niemals erreicht.
  5. Rückrufe beobachten – Sie müssen Beobachtungsrückrufe verwenden, um über Änderungen in den Eigenschaftswerten von benutzerdefinierten Steuerelementen benachrichtigt zu werden.

Wenn keine der oben aufgeführten Vorbehalte beobachtet wird, kann die Bindung zur Laufzeit automatisch fehlschlagen.

Objective-C veränderbares Muster und Eigenschaften

Objective-C Frameworks verwenden ein Idiom, bei dem einige Klassen mit einer änderbaren Unterklasse unveränderlich sind. Die unveränderliche Version ist z. B NSString . die Unterklasse, NSMutableString die Mutation zulässt.

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

Die Art und Weise, wie dies C# zugeordnet ist, besteht darin, sowohl den Getter als auch den Setter für die Basisklasse hinzuzufügen, aber den Setter mit einem [NotImplemented] Attribut.

Verwenden Sie dann in der änderbaren Unterklasse die [Override] attribut on the property to ensure that the property is actually overriding the parent's behavior.

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 Foo, wird generiert:

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

Für Konstruktoren, die Sie definieren, müssen sie mithilfe der folgenden Signatur innerhalb der Schnittstellendefinition deklariert werden: Sie müssen einen IntPtr Wert zurückgeben und der Name der Methode Konstruktor sein. 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 "Modelle und Protokolle" die Objective-C Protokolle klassen zu, die mit den [Model] Attribut. Dies wird in der Regel beim Implementieren von Objective-C Delegatenklassen verwendet.

Der große Unterschied zwischen einer regulären gebundenen Klasse und einer Delegatenklasse besteht darin, dass die Delegatklasse eine oder mehrere optionale Methoden aufweisen kann.

Betrachten Sie beispielsweise die UIKit Klasse UIAccelerometerDelegate, wie sie in Xamarin.iOS 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 UIAccelerometerDelegate ist, ist nichts anderes zu tun. Wenn es jedoch eine erforderliche Methode für das Protokoll gab, sollten Sie die [Abstract] attribut to the method. Dadurch wird der Benutzer der Implementierung erzwingen, tatsächlich einen Textkörper für die Methode bereitzustellen.

Im Allgemeinen werden Protokolle in Klassen verwendet, die auf Nachrichten reagieren. Dies geschieht in der Regel Objective-C durch Zuweisen der "delegate"-Eigenschaft zu einer Instanz eines Objekts, das auf die Methoden im Protokoll antwortet.

Die Konvention in Xamarin.iOS besteht darin, sowohl die Objective-C lose gekoppelte Formatvorlage zu unterstützen, bei der jeder Instanz einer NSObject Stellvertretung zugewiesen werden kann, als auch eine stark typierte Version davon verfügbar zu machen. Aus diesem Grund stellen wir in der Regel sowohl eine Delegate Eigenschaft bereit, die stark typiert ist, als auch eine WeakDelegate lose typierte Eigenschaft. In der Regel binden wir die lose typierte Version an [Export], und wir verwenden das [Wrap] Attribut, um die stark typierte 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 Idioms für die Übernahme Objective-C eines oder mehrerer Protokolle in einer bestimmten Klasse.

Für jede Protokolldefinition MyProtocol in Objective-Cist nun eine IMyProtocol Schnittstelle vorhanden, die alle erforderlichen Methoden aus dem Protokoll sowie eine Erweiterungsklasse auflistet, die alle optionalen Methoden bereitstellt. In Kombination mit neuer 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 und Weise, wie Sie Protokolle nutzen, 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 außer Kraft setzen und die vollständige Typsicherheit erhalten können. Da C# jedoch nicht mehrere Vererbung unterstützt, gibt es Szenarien, in denen Sie möglicherweise über eine andere Basisklasse verfügen müssen, aber Sie möchten dennoch eine Schnittstelle implementieren, in der sich die

Die generierte Schnittstellendefinition wird eingegrenzt. Es handelt sich um eine Schnittstelle mit allen erforderlichen Methoden aus dem Protokoll. Auf diese Weise können Entwickler, die Ihr Protokoll implementieren möchten, lediglich die Schnittstelle implementieren. Die Laufzeit registriert den Typ automatisch bei der Ü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, die vollständige Typüberprüfung für die erforderlichen Methoden erhalten, aber für die optionalen Protokollmethoden auf schwache Typisierung (manuell mithilfe von [Export] Attributen und Abgleich der Signatur) zurückgreifen müssen.

Um eine API zu nutzen, 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 mit allen Methoden behandeln können.

Wenn Sie die Protokolldefinitionen in Ihrer API verwenden möchten, müssen Sie Skelett leere Schnittstellen in Ihrer API-Definition schreiben. Wenn Sie myProtocol in einer API verwenden möchten, müssen Sie dies 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 ();
}

Die oben genannten Punkte sind erforderlich, da dies zum Zeitpunkt der IMyProtocol Bindung nicht vorhanden wäre, weshalb Sie eine leere Schnittstelle bereitstellen müssen.

Übernehmen von protokollgenerierten Schnittstellen

Wann immer 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, daher entspricht sie folgendem:

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 sie kennen sollten. 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 hinzufügen, beginnend mit der Außerkraftsetzung.

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 (z. B. NSUrlSessionDownloadDelegate) stellen alle erforderlichen und optionalen Selektoren bereit, die angemessene Standardwerte zurückgeben.
  • Benutzer der Schnittstelle (INSUrlSessionDownloadDelegate im Beispiel) reagieren nur auf die angegebenen genauen Selektoren.

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

Bindungsklassenerweiterungen

Es Objective-C ist 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 beim Importieren als Methoden in der NSStringDrawingExtensionsDatei definiert NSString UIKit sind:

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

Bindungsargumentlisten Objective-C

Objective-C unterstützt variadische Argumente. Zum Beispiel:

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

Um diese Methode aus C# aufzurufen, möchten Sie eine Signatur wie folgt erstellen:

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

Dadurch wird die Methode als intern deklariert, die oben genannte API von Benutzern ausgeblendet, aber der Bibliothek verfügbar. Anschließend können Sie eine Methode wie folgt schreiben:

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

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 und als Schlüssel in Wörterbüchern darstellen.

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 Symbols zum Nachschlagen. Zum Beispiel:

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

Wenn Sie verschiedene Felder in einer statischen Klasse umbrechen möchten, die nicht von NSObjectdieser abgeleitet wird, können Sie die [Static] Attribut für die Klasse, wie folgt:

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

Die obigen generieren eine LonelyClass , die nicht von NSObject der abgeleitet wird und eine Bindung an die NSSomeEventNotificationNSString verfügbar gemachte als ein NSString.

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 eine Verknüpfung herstellen, gibt es keine Bibliothek, an die eine Bindung besteht, daher müssen Sie den __Internal Namen verwenden:

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

Bindungsenumen

Sie können Ihre Bindungsdateien direkt hinzufügen enum , damit sie in API-Definitionen einfacher verwendet werden können , 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, ein [Internal] Attribut zu dekorierenvoid Perform (NSString mode);. Dadurch wird die konstantenbasierte API von Ihren Bindungskunden ausgeblendet .

Dies würde jedoch die Unterklassifizierung des Typs einschränken, da bei der alternativen API ein [Wrap] Attribut verwendet wird. Diese generierten Methoden sind nicht virtualmöglich, d. h. Sie können sie nicht überschreiben , was eine gute Wahl sein kann oder nicht.

Eine Alternative besteht darin, die ursprüngliche, NSString-basierte Definition als .[Protected] Dadurch können Unterklassen bei Bedarf funktionieren, und die umgebrochene Version funktioniert weiterhin und ruft die Außerkraftsetzungsmethode auf.

Bindung NSValue, NSNumber, und NSString zu einem besseren Typ

Das [BindAs] Attribut ermöglicht die Bindung NSValue NSNumberund NSString(Enumerationen) in genauere C#-Typen. Das Attribut kann verwendet werden, um eine bessere, genauere .NET-API über die systemeigene API zu erstellen.

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

Zum Beispiel:

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

Würde folgendes ausgeben:

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

Intern werden wir die bool?<>NSNumber - und<CGRect ->NSValue Konvertierungen tun.

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

Zum Beispiel:

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

Würde folgendes ausgeben:

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

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

Weitere Informationen finden Sie in der [BindAs] Dokumentation, um unterstützte Konvertierungstypen anzuzeigen.

Binden von Benachrichtigungen

Benachrichtigungen sind Nachrichten, die in den NSNotificationCenter.DefaultCenter Nachrichten gepostet 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 des NSNotificationCenters. Wenn eine Anwendung eine Nachricht im Benachrichtigungscenter sendet, enthält sie in der Regel eine Nutzlast, die im NSNotification.UserInfo-Wörterbuch gespeichert ist. Dieses Wörterbuch ist schwach typiert und das Abrufen von Informationen ist fehleranfällig, da Benutzer in der Regel in der Dokumentation lesen müssen, welche Schlüssel für das Wörterbuch verfügbar sind, und die Typen der Werte, die 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. Dazu legen Sie die [Notification] Attribut für eine Eigenschaft, die auch mit einem Markiert wurde [Field] eigenschaft (es kann öffentlich oder privat sein).

Dieses Attribut kann ohne Argumente für Benachrichtigungen verwendet werden, die keine Nutzlast enthalten, oder Sie können ein Attribut angeben, das auf eine System.Type andere Schnittstelle in der API-Definition verweist, in der Regel mit dem Namen, der mit "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 Nachschlagen des Objective-C Wörterbuchs zum Abrufen des Werts verwendet wird.

Zum 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 ganz einfach Benachrichtigungen abonnieren, die im NSDefaultCenter gepostet wurden, indem Sie Code wie folgt verwenden:

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

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

token.Dispose ();

Oder Sie können NSNotification.DefaultCenter.RemoveObserver aufrufen und das Token übergeben. Wenn Ihre Benachrichtigung Parameter enthält, sollten Sie wie folgt eine Hilfsschnittstelle EventArgs 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; }
}

Im obigen Code wird eine MyScreenChangedEventArgs Klasse mit den ScreenX Eigenschaften generiert ScreenY , die die Daten aus dem NSNotification.UserInfo-Wörterbuch mit den Schlüsseln "ScreenXKey" bzw. "ScreenYKey" abrufen und die richtigen Konvertierungen anwenden. Das [ProbePresence] Attribut wird verwendet, um den Generator zu untersuchen, wenn der Schlüssel im UserInfoSchlüssel festgelegt 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).

Auf diese Weise 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 (z NSObject. B. ) zu erweitern, wenn ein bestimmtes Framework verknüpft ist (z. B UIKit. ), indem sie ihre Methoden verfügbar 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 den C#-Erweiterungsmethoden. Dies würde eine Kategorie wie folgt Objective-Caussehen:

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

Das obige Beispiel, das in einer Bibliothek gefunden wird, würde Instanzen der UIView Methode makeBackgroundRederweitern.

Um diese zu binden, können Sie das [Category] Attribut für eine Schnittstellendefinition verwenden. Bei Verwendung der [Category] attribut, die Bedeutung der [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 ();
}

Im obigen Abschnitt 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 erhalten Objective-Cwürden. In einigen anderen Fällen werden Kategorien nicht verwendet, um eine Systemklasse zu erweitern, sondern um Funktionen ausschließlich für Dekorationszwecke zu organisieren. Dies sieht folgendermaßen aus:

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

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

Obwohl Sie die [Category] Attribut auch für diese Dekorationsart von Deklarationen, können Sie sie auch einfach der Klassendefinition hinzufügen. Beide würden dasselbe 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 die funktionale Entsprechung anonymer C#-Methoden zu Objective-Cbringen. Die NSSet Klasse macht beispielsweise diese Methode verfügbar:

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

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

Um diese Art von API mit btouch zu binden, müssen Sie zuerst die Blocktypsignatur als C#-Delegat deklarieren und dann 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, benutzerfreundliche Methoden umwandeln (Methoden, die einen Task oder Task<T> zurückgeben).

Sie können verwenden,[Async] attribut on methods that return void and whose last argument is a callback. 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 den ResultType gewünschten Namen des generierten Typs festlegen oder ResultTypeName angeben, 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:

[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 typierte NSDictionary APIs mit bestimmten Schlüsseln und Werten übergeben, aber diese sind fehleranfällig (Sie können ungültige Schlüssel übergeben und keine Warnungen erhalten; Sie können ungültige Werte übergeben und keine Warnungen erhalten) und frustrierend zu verwenden, da sie mehrere Reisen in die Dokumentation erfordern, um die möglichen Schlüsselnamen und -werte nachzuschlagen.

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

Wenn die Objective-C API z. B. einen NSDictionary akzeptiert und als Schlüssel XyzVolumeKey dokumentiert wird, der einen Volumewert von 0,0 bis 1.0 einnimmt NSNumber und eine XyzCaptionKey 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 nullwertiger Float-Wert definiert, da die Konvention in Objective-C diesen Wörterbüchern nicht den Wert aufweisen muss. Daher gibt es Szenarien, in denen der Wert möglicherweise nicht festgelegt wird.

Dazu müssen Sie ein paar Dinge tun:

  • Erstellen Sie eine stark typistische Klasse, die DictionaryContainer unterklassen und die verschiedenen Getters und Setter für jede Eigenschaft bereitstellt.
  • Deklarieren Sie Überladungen für die Methoden, die für die neue stark typierte Version verwendet NSDictionary werden.

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

Sie müssen hierfür eine Unterstützende Datei erstellen, sie wird nicht in Ihre Vertrags-API eingefügt. Dies wäre das, was Sie schreiben müssen, 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 über der API auf niedriger Ebene anzeigt.

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

Wenn Ihre API nicht überschrieben werden muss, können Sie die NSDictionary-basierte API mithilfe der [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 lässt auch zu, dass NULL übergeben werden kann.

Nun, eine Sache, die wir nicht erwähnt haben, ist, wo die XyzOptionsKeys Werte stammen. In der Regel gruppieren Sie die Schlüssel, die eine API in einer statischen Klasse XyzOptionsKeyswie folgt anzeigt:

[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 typierten Wörterbücher an. Dies vermeidet viele Textbausteine, und Sie können das Wörterbuch direkt in Ihrem API-Vertrag definieren, anstatt eine externe Datei zu verwenden.

Um ein stark typiertes Wörterbuch zu erstellen, führen Sie eine Schnittstelle in Ihrer API ein und versehen sie mit dem StrongDictionary-Attribut . Dadurch wird dem Generator mitgeteilt, dass eine Klasse mit demselben Namen wie die Schnittstelle erstellt werden soll, von der sie abgeleitet DictionaryContainer wird, und stellt dafür starke typierte Accessoren bereit.

Das [StrongDictionary] Attribut verwendet einen Parameter, bei dem es sich um den Namen der statischen Klasse handelt, die Ihre Wörterbuchschlüssel enthält. Dann wird jede Eigenschaft der Schnittstelle zu einem stark typierten 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 das Erstellen eines stark typierten Accessors keine externe Datei mehr erfordert oder manuell Getter und Setter für jede Eigenschaft erstellen muss oder die Schlüssel manuell selbst nachschlagen muss.

So sieht ihre gesamte Bindung aus:

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

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 einer [Export] attribut with the name that you want to use.

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 Welt "CocoaTouch" der Objective-C Welt "Xamarin.iOS" zuordnen sollten:

Objective-C Typname Xamarin.iOS Unified 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-Laufzeit übernimmt automatisch die Konvertierung von C#-Arrays in NSArrays und führt die Konvertierung zurück, z. B. die imaginäre Objective-C Methode, die eine NSArray von UIViews:

// 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 typiertes C#-Array zu verwenden, da die IDE die ordnungsgemäße Codevervollständigung mit dem tatsächlichen Typ ermöglicht, ohne dass der Benutzer erraten muss, oder die Dokumentation nachschlagen, um den tatsächlichen Typ der im Array enthaltenen Objekte zu ermitteln.

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

Selektoren

Selektoren werden in der Objective-C API als spezieller Typ SELangezeigt. Beim Binden einer Auswahl 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. Die Bereitstellung beider Beiden entspricht im Grunde dem C#-Delegaten: Etwas, das sowohl die methode, die aufgerufen werden soll, als auch das Objekt, in dem die Methode aufgerufen werden soll, kapselt.

So sieht die Bindung aus:

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

Und so würde die Methode in der Regel in einer Anwendung verwendet werden:

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

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

Um die Bindung an C#-Entwickler ansprechender zu gestalten, stellen Sie in der Regel eine Methode bereit, die einen NSAction Parameter verwendet, mit dem C#-Stellvertretungen und Lambda-Stellvertretungen anstelle der Target+Selector. Dazu blenden Sie die Methode in der SetTarget Regel aus, indem Sie sie mit einer [Internal] attribut and then you would expose a new helper method, like this:

// 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 eine NSStringMethode verwendet, können Sie dies durch einen C#-Zeichenfolgentyp ersetzen, sowohl für Rückgabetypen als auch für Parameter.

Der einzige Fall, wenn Sie einen NSString direkten Verwenden möchten, besteht darin, dass die Zeichenfolge als Token verwendet wird. Weitere Informationen zu Zeichenfolgen und zum API-Design im NSString-Dokument finden Sie unter .for more information about strings and NSString, read the API Design on NSString document.

In einigen seltenen Fällen kann eine API eine C-ähnliche Zeichenfolge () anstelle einer Objective-C Zeichenfolge (char *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 anhand eines Verweises.

In der Regel sieht die Signatur wie folgt aus:

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

Das erste Beispiel zeigt einen allgemeinen Objective-C Idiom, um Fehlercodes zurückzugeben, ein Zeiger auf einen NSError Zeiger wird übergeben, und wenn der Wert zurückgegeben wird. Die zweite Methode zeigt, wie eine Objective-C Methode ein Objekt annehmen und dessen Inhalt ändern kann. Dies wäre ein Durchlauf nach Verweis, anstatt ein reiner 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);

Speicherverwaltungsattribute

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

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

Der obige Wert würde als "Retain"-Semantik kennzeichnen. Die verfügbaren Semantiken sind:

  • Zuweisen
  • Kopieren
  • Beibehalten V

Stilrichtlinien

Verwenden von [Intern]

Sie können verwenden,[Internal] attribut to hide a method from the public API. 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 im Bindungsgenerator Einschränkungen auftreten, z. B. in einigen erweiterten Szenarien können Typen verfügbar gemacht werden, 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 übertragen in der Regel Benachrichtigungen oder Anfordern von Informationen durch Senden einer Nachricht an eine Stellvertretungsklasse (Objective-C Stellvertretung).

Dieses Modell, das von Xamarin.iOS vollständig unterstützt und angezeigt wird, kann 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. Auf diese Weise kann Code wie folgt ausgeführt werden:

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

Der Bindungsgenerator kann die Eingabemenge verringern, die erforderlich ist, um das Objective-C Muster dem C#-Muster zuzuordnen.

Ab Xamarin.iOS 1.4 kann der Generator auch anweisen, Bindungen für eine bestimmte Objective-C Stellvertretung zu erstellen und den Delegaten als C#-Ereignisse und -Eigenschaften für den Hosttyp verfügbar zu machen.

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

Berücksichtigen Sie die folgende Einrichtung:

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

Um die Klasse umzuschließen, müssen Sie Folgendes ausführen:

  • Fügen Sie in Ihrer Hostklasse Zu Ihrer [BaseType]
    deklarieren Sie den Typ, der als Delegat fungiert, und den C#-Namen, den Sie verfügbar gemacht haben. In unserem obigen Beispiel sind typeof (MyClassDelegate) bzw WeakDelegate . diese.
  • In Ihrer Delegatklasse müssen Sie für jede Methode, die mehr als zwei Parameter enthält, 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 umzuschließen. Es ist möglich, dass einige Objective-C Klassen Nachrichten an mehrere Stellvertretungen ausgeben, sodass Sie Arrays bereitstellen müssen, um dieses Setup zu unterstützen. Die meisten Setups benötigen sie nicht, aber der Generator ist bereit, diese Fälle zu unterstützen.

Der resultierende Code sieht wie folgt aus:

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

Dies EventArgs wird verwendet, um den Namen der EventArgs zu generierenden Klasse anzugeben. Sie sollten eine pro Signatur verwenden (in diesem Beispiel enthält dies 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;
}

So können Sie jetzt den Code 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 anstelle mehrerer potenzieller Abonnenten (z. B. mehrere Methoden einen Hook in ein Clicked Ereignis oder ein DownloadFinished Ereignis) Rückrufe nur über einen einzelnen Abonnenten verfügen können.

Der Prozess ist identisch, der einzige Unterschied besteht darin, dass anstelle des Namens der EventArgs klasse, die generiert wird, das EventArgs tatsächlich verwendet wird, um den resultierenden C#-Delegatennamen zu benennen.

Wenn die Methode in der Delegatenklasse einen Wert zurückgibt, ordnet der Bindungsgenerator dies 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 nicht mit dem Delegaten in Verbindung steht. Dazu verwenden Sie die [DefaultValue] oder [DefaultValueFromArgument] Attribute.

[DefaultValue] hartcodiert 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 nicht direkt vom btouch-Schnittstellendefinitionssystem 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 die ausführbare Datei mit diesen Bibliotheken verknüpft ist.

Sie müssen Xamarin.iOS darüber informieren, wie Sie Ihre Bibliotheken verknüpfen. Dazu können Sie entweder die Buildkonfiguration ändern, um den mtouch Befehl mit zusätzlichen Buildargumenten aufzurufen, die angeben, wie sie mit den neuen Bibliotheken mithilfe der Option "-gcc_flags" verknüpft werden, gefolgt von einer zitierten Zeichenfolge, 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 wird die Frameworkbibliothek mit ihrer CFNetwork endgültigen ausführbaren Datei verknüpft.libMyLibrary.alibSystemLibrary.dylib

Oder Sie können die Assemblyebene [LinkWithAttribute]nutzen, die Sie in Ihre Vertragsdateien einbetten können (z AssemblyInfo.cs. B. ). Wenn Sie die [LinkWithAttribute]Datei verwenden, müssen Sie ihre systemeigene Bibliothek zum Zeitpunkt der Bindung verfügbar haben, da dies die systemeigene Bibliothek in Ihre Anwendung einbettet. Zum 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 Befehl benötigen -force_load , und der Grund dafür ist, dass das Flag "-ObjC" zwar kompiliert wird, aber die Metadaten, die erforderlich sind, um Kategorien zu unterstützen (der Linker/Compiler, die Totcode eliminierungsstreifen), die Sie zur Laufzeit für Xamarin.iOS benötigen.

Unterstützte Referenzen

Einige vorübergehende Objekte wie Aktionsblätter und Warnungsfelder sind umständlich, um Entwickler auf dem Laufenden zu halten, und der Bindungsgenerator kann hier etwas helfen.

Wenn Sie z. B. eine Klasse hatten, die eine Nachricht anzeigte und dann ein Done Ereignis generiert habe, wäre dies die herkömmliche Art der 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 die Referenz für das Feld selbst löschen oder aktiv löschen. Während der Bindungscode unterstützt der Generator, den Verweis für Sie nachzuverfolgen und zu löschen, wenn eine spezielle Methode aufgerufen wird, würde der obige Code dann wie folgt aussehen:

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

Beachten Sie, dass es nicht mehr erforderlich ist, die Variable in einer Instanz 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 Ereigniseigenschaft in der [BaseType] Deklaration und auch die KeepUntilRef Variable auf den Namen der Methode festlegen, 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);
}

Erben von Protokollen

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

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