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:
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:
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.cs
StructsAndEnums.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:
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:
- Bindungseigenschaften müssen statisch sein : Beim Definieren der Bindung von Eigenschaften muss das
[Static]
-Attribut verwendet werden. - 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.
- 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.
- Haltepunkte und der Getter/Setter : Haltepunkte, die in den Getter- oder Settermethoden der -Eigenschaft platziert werden, werden nie erreicht.
- 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 Foo
generiert 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 NSStringDrawingExtensions
werden, 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 NSObject
abgeleitet 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 NSSomeEventNotification
NSString
als NSString
verfü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
, NSNumber
und 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 NSNumber
NSValue
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 UserInfo
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).
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 makeBackgroundRed
erweitern.
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 block
akzeptiert. 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 ResultType
ResultTypeName
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 XyzOptionsKeys
folgt 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 UIViews
zurü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 SEL
angezeigt. 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+Selector
verwendet 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 NSString
verwendet, 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 NSString
finden 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 sindtypeof (MyClassDelegate)
dies undWeakDelegate
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 NSObject
erben.
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.