Vazby Objective-C knihoven

Při práci s Xamarin.iOS nebo Xamarin.Mac můžete narazit na případy, kdy chcete využívat knihovnu třetí strany Objective-C . V těchto situacích můžete pomocí projektů vazeb Xamarin vytvořit vazbu jazyka C# s nativními Objective-C knihovnami. Projekt používá stejné nástroje, které používáme k přenesení rozhraní API pro iOS a Mac do jazyka C#.

Tento dokument popisuje, jak vytvořit vazbu Objective-C rozhraní API, pokud vytváříte vazbu pouze rozhraní API jazyka C, měli byste pro toto rozhraní použít standardní mechanismus .NET, rozhraní P/Invoke Framework. Podrobnosti o tom, jak staticky propojit knihovnu jazyka C, jsou k dispozici na stránce Propojení nativních knihoven .

Podívejte se na naši doprovodnou referenční příručku k typům vazeb. Pokud se navíc chcete dozvědět více o tom, co se děje pod kapotou, podívejte se na stránku Přehled vazeb.

Vazby lze sestavit pro knihovny pro iOS i Mac. Tato stránka popisuje, jak pracovat na vazbě iOS, ale vazby Mac jsou velmi podobné.

Ukázkový kód pro iOS

Ukázkový projekt vazby pro iOS můžete použít k experimentování s vazbami.

Začínáme

Nejjednodušší způsob, jak vytvořit vazbu, je vytvořit projekt vazby Xamarin.iOS. Můžete to udělat z Visual Studio pro Mac tak, že vyberete typ projektu, knihovnu vazeb knihovny > pro iOS>:

Do this from Visual Studio for Mac by selecting the project type, iOS Library Bindings Library

Vygenerovaný projekt obsahuje malou šablonu, kterou můžete upravit, obsahuje dva soubory: ApiDefinition.cs a StructsAndEnums.cs.

Tady ApiDefinition.cs definujete kontrakt rozhraní API, jedná se o soubor, který popisuje, jak se podkladové Objective-C rozhraní API promítá do jazyka C#. Syntaxe a obsah tohoto souboru jsou hlavním tématem diskuze o tomto dokumentu a jeho obsah je omezen na rozhraní jazyka C# a deklarace delegátů jazyka C#. Soubor StructsAndEnums.cs je soubor, ve kterém zadáte definice, které vyžadují rozhraní a delegáti. To zahrnuje hodnoty výčtu a struktury, které může váš kód použít.

Vytvoření vazby rozhraní API

Pokud chcete provést komplexní vazbu, budete chtít porozumět Objective-C definici rozhraní API a seznámit se s pokyny k návrhu rozhraní .NET Framework.

Pro vytvoření vazby knihovny obvykle začnete souborem definice rozhraní API. Definiční soubor rozhraní API je pouze zdrojový soubor jazyka C#, který obsahuje rozhraní jazyka C#, která byla opatřena poznámkami s několika atributy, které pomáhají řídit vazbu. Tento soubor definuje, co je kontrakt mezi jazykem C# a Objective-C co je.

Jedná se například o triviální soubor rozhraní API pro knihovnu:

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

Výše uvedený vzorek definuje třídu, která je odvozena Cocos2D.Camera ze základního NSObject typu (tento typ pochází Foundation.NSObject) a která definuje statickou vlastnost (ZEye), dvě metody, které neobsahují argumenty, a metodu, která přebírá tři argumenty.

Podrobné informace o formátu souboru rozhraní API a o atributech, které můžete použít, najdete v části Definiční soubor rozhraní API níže.

Pokud chcete vytvořit úplnou vazbu, obvykle se budete zabývat čtyřmi komponentami:

  • Definiční soubor rozhraní API (ApiDefinition.cs v šabloně).
  • Volitelné: všechny výčty, typy, struktury vyžadované definičním souborem rozhraní API (StructsAndEnums.cs v šabloně).
  • Volitelné: Další zdroje, které mohou rozšířit vygenerovanou vazbu, nebo poskytnout popisnější rozhraní API jazyka C# (všechny soubory jazyka C#, které přidáte do projektu).
  • Nativní knihovna, kterou vytváříte vazbu.

Tento graf znázorňuje vztah mezi soubory:

This chart shows the relationship between the files

Soubor definice rozhraní API bude obsahovat pouze obory názvů a definice rozhraní (se všemi členy, které rozhraní může obsahovat) a neměl by obsahovat třídy, výčty, delegáty nebo struktury. Definiční soubor rozhraní API je pouze kontrakt, který se použije k vygenerování rozhraní API.

Jakýkoli další kód, který potřebujete, jako jsou výčty nebo podpůrné třídy, by se měl hostovat v samostatném souboru, v příkladu nad "Kamera Mode" je hodnota výčtu, která v souboru CS neexistuje a měla by být hostována v samostatném souboru, napříkladStructsAndEnums.cs:

public enum CameraMode {
    FlyOver, Back, Follow
}

Soubor APIDefinition.cs se zkombinuje s StructsAndEnum třídou a slouží k vygenerování základní vazby knihovny. Výslednou knihovnu můžete použít tak, jak je, ale obvykle budete chtít výslednou knihovnu vyladit a přidat některé funkce jazyka C# pro výhody uživatelů. Mezi příklady patří implementace ToString() metody, poskytnutí indexerů jazyka C#, přidání implicitních převodů do a z některých nativních typů nebo poskytnutí verzí některých metod se silnými typy. Tato vylepšení jsou uložená v dalších souborech C#. Stačí do projektu přidat soubory jazyka C#, které budou součástí tohoto procesu sestavení.

To ukazuje, jak byste implementovali kód v Extra.cs souboru. Všimněte si, že budete používat částečné třídy, protože tyto rozšiřují částečné třídy, které jsou generovány z kombinace ApiDefinition.cs a StructsAndEnums.cs základní vazby:

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

Sestavení knihovny vytvoří vaši nativní vazbu.

K dokončení této vazby byste měli do projektu přidat nativní knihovnu. Můžete to udělat tak, že do projektu přidáte nativní knihovnu, a to buď přetažením nativní knihovny z Finderu do projektu v Průzkumníku řešení, nebo kliknutím pravým tlačítkem myši na projekt a výběrem možnosti Přidat>soubory vyberte nativní knihovnu. Nativní knihovny podle konvence začínají slovem "lib" a končí příponou ".a". Když to uděláte, Visual Studio pro Mac přidá dva soubory: soubor .a automaticky vyplněný soubor C#, který obsahuje informace o tom, co nativní knihovna obsahuje:

Native libraries by convention start with the word lib and end with the extension .a

libMagicChord.linkwith.cs Obsah souboru obsahuje informace o tom, jak lze tuto knihovnu použít, a dává integrovanému vývojovému prostředí (IDE) pokyn, aby tento binární soubor zabalil do výsledného souboru DLL:

using System;
using ObjCRuntime;

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

Úplné podrobnosti o tom, jak používat [LinkWith]atribut jsou zdokumentované v průvodci odkazem na typy vazeb.

Teď, když vytvoříte projekt, skončíte se souborem MagicChords.dll , který obsahuje vazbu i nativní knihovnu. Tento projekt nebo výslednou knihovnu DLL můžete distribuovat ostatním vývojářům pro vlastní použití.

Někdy můžete zjistit, že potřebujete několik hodnot výčtu, definice delegátů nebo jiné typy. Neumisťujte je do souboru definic rozhraní API, protože se jedná pouze o kontrakt.

Definiční soubor rozhraní API

Definiční soubor rozhraní API se skládá z řady rozhraní. Rozhraní v definici rozhraní API budou převedena na deklaraci třídy a musí být zdobena atributem [BaseType] k určení základní třídy třídy.

Možná vás zajímá, proč jsme nepoužili třídy místo rozhraní pro definici kontraktu. Vybrali jsme rozhraní, protože nám umožnilo napsat kontrakt pro metodu, aniž bychom museli do definičního souboru rozhraní API zadat tělo metody nebo museli poskytnout tělo, které muselo vyvolat výjimku nebo vrátit smysluplnou hodnotu.

Ale vzhledem k tomu, že používáme rozhraní jako kostru k vygenerování třídy, museli jsme se uchylovat k dekorování různých částí kontraktu s atributy pro řízení vazby.

Metody vazby

Nejjednodušší vazbou, kterou můžete provést, je vytvořit vazbu metody. Stačí deklarovat metodu v rozhraní s konvencemi pojmenování jazyka C# a vyzdobit metodu pomocí Atribut [Export]. Atribut [Export] je to, co propojí název jazyka C# s Objective-C názvem v modulu runtime Xamarin.iOS. Parametr [Export] atribut je název selektoru Objective-C . Některé příklady:

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

Výše uvedené ukázky ukazují, jak můžete svázat metody instance. Pokud chcete vytvořit vazbu statických metod, musíte použít [Static] atribut, například takto:

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

To je povinné, protože kontrakt je součástí rozhraní a rozhraní nemají žádnou představu o statických vs. deklarací instancí, takže je nutné znovu použít atributy. Pokud chcete skrýt konkrétní metodu z vazby, můžete metodu ozdobit atributem [Internal] .

Příkaz btouch-native zavede kontroly parametrů odkazu, které nemají hodnotu null. Pokud chcete pro konkrétní parametr povolit hodnoty null, použijte [NullAllowed] atribut u parametru, například takto:

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

Při exportu typu odkazu můžete pomocí klíčového [Export] slova zadat také sémantiku přidělení. To je nezbytné k zajištění úniku dat.

Vlastnosti vazeb

Stejně jako metody Objective-C jsou vlastnosti svázané pomocí [Export] a namapovat je přímo na vlastnosti jazyka C#. Stejně jako metody lze vlastnosti dekorovat pomocí [Static] a [Internal] Atributy.

Když použijete atribut u [Export] vlastnosti pod kryt btouch-native ve skutečnosti vázán dvě metody: getter a setter. Název, který zadáte k exportu , je základní název a setter se vypočítá tak, že předpnete slovo "set", převede první písmeno základního názvu na velké písmeno a selektor vezme argument. To znamená, že [Export ("label")] použité u vlastnosti ve skutečnosti váže metody "label" a "setLabel:" Objective-C .

Objective-C Někdy vlastnosti nedodržují výše popsaný vzor a název je přepsán ručně. V těchto případech můžete řídit způsob generování vazby pomocí [Bind] atributu getter nebo setter, například:

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

Tím se vytvoří vazba "isMenuVisible" a "setMenuVisible:". Volitelně lze vlastnost svázat pomocí následující syntaxe:

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

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

Kde jsou getter a setter explicitně definovány jako ve name výše uvedených vazbách setName .

Kromě podpory statických vlastností pomocí [Static], můžete ozdobit vlastnosti vlákna statické pomocí [IsThreadStatic], například:

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

Stejně jako metody umožňují označení některých parametrů příznakem [NullAllowed], můžete použít [NullAllowed] na vlastnost označující, že null je platná hodnota vlastnosti, například:

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

Parametr [NullAllowed] lze také zadat přímo na zařízení setter:

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

Upozornění vazby vlastních ovládacích prvků

Při nastavování vazby pro vlastní ovládací prvek byste měli zvážit následující upozornění:

  1. Vlastnosti vazby musí být statické – při definování vazby vlastností [Static] se musí použít atribut.
  2. Názvy vlastností se musí přesně shodovat – název použitý k vytvoření vazby vlastnosti musí přesně odpovídat názvu vlastnosti ve vlastním ovládacím prvku.
  3. Typy vlastností se musí přesně shodovat – Typ proměnné použité k vytvoření vazby vlastnosti musí přesně odpovídat typu vlastnosti ve vlastním ovládacím prvku.
  4. Zarážky a getter/setter – zarážky umístěné v metodách getter nebo setter vlastnosti se nikdy nedostanou.
  5. Sledujte zpětná volání – Budete muset použít zpětná volání pozorování, abyste byli upozorněni na změny hodnot vlastností vlastních ovládacích prvků.

Pokud se nepodaří sledovat některé z výše uvedených upozornění, může dojít k tichému selhání vazby za běhu.

Objective-C proměnlivý vzor a vlastnosti

Objective-C architektury používají idiom, kde některé třídy jsou neměnné s proměnlivou podtřídou. Jedná se například NSString o neměnnou verzi, zatímco NSMutableString je podtřída, která umožňuje mutaci.

V těchtotřídch A pro proměnlivou verzi zavést setter. Vzhledem k tomu, že to u jazyka C# není možné, museli jsme tento idiom namapovat na idiom, který by fungoval s jazykem C#.

Způsob mapování na jazyk C# spočívá v přidání metody getter i setter do základní třídy, ale označení setter příznakem Atribut [NotImplemented].

Potom na proměnlivé podtřídě použijete [Override] atributu u vlastnosti, aby se zajistilo, že vlastnost ve skutečnosti přepisuje chování nadřazeného objektu.

Příklad:

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

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

Konstruktory vazeb

Nástroj btouch-native automaticky vygeneruje čtyři konstruktory ve vaší třídě pro danou třídu Foo, vygeneruje:

  • Foo (): výchozí konstruktor (mapuje se na Objective-Ckonstruktor init)
  • Foo (NSCoder): konstruktor použitý při deserializaci souborů NIB (mapuje se na Objective-C"initWithCoder:" konstruktor).
  • Foo (IntPtr handle): konstruktor pro vytváření na základě popisovače, je vyvolán modulem runtime, když modul runtime potřebuje zveřejnit spravovaný objekt z nespravovaného objektu.
  • Foo (NSEmptyFlag): Používá se odvozenými třídami, aby se zabránilo dvojité inicializaci.

Pro konstruktory, které definujete, musí být deklarovány pomocí následujícího podpisu uvnitř definice rozhraní: musí vrátit IntPtr hodnotu a název metody by měl být Konstruktor. Například k vytvoření vazby konstruktoru initWithFrame: byste použili toto:

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

Vazbové protokoly

Jak je popsáno v dokumentu návrhu rozhraní API, v části popisující modely a protokoly Xamarin.iOS mapuje Objective-C protokoly na třídy, které byly označeny příznakem Atribut [Model]. Obvykle se používá při implementaci Objective-C tříd delegáta.

Velký rozdíl mezi běžnou vázanou třídou a třídou delegáta spočívá v tom, že třída delegáta může mít jednu nebo více volitelných metod.

Představte si UIKit například třídu UIAccelerometerDelegate, takto je vázána v Xamarin.iOS:

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

Vzhledem k tomu, že se jedná o volitelnou metodu pro definici UIAccelerometerDelegate , není k dispozici nic jiného. Pokud ale v protokolu byla požadovaná metoda, měli byste přidat [Abstract] atributu pro metodu. Tím se vynutí, aby uživatel implementace skutečně poskytl tělo pro metodu.

Obecně platí, že protokoly se používají ve třídách, které reagují na zprávy. Obvykle se to provádí Objective-C přiřazením vlastnosti "delegate" instanci objektu, který reaguje na metody v protokolu.

Konvence v Xamarin.iOS je podporovat jak Objective-C volně propojený styl, kde lze delegátovi přiřadit libovolnou instanci nějaké instance NSObject , a také vystavit verzi silného typu. Z tohoto důvodu obvykle poskytujeme vlastnost silného Delegate typu i WeakDelegate volně napsanou vlastnost. Obvykle svážeme volně napsanou verzi s atributem [Export]a pomocí atributu [Wrap] poskytujeme verzi silného typu.

To ukazuje, jak jsme sváželi UIAccelerometer třídu:

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

Novinka v MonoTouchu 7.0

Počínaje verzí MonoTouch 7.0 byla začleněna nová a vylepšená funkce vazby protokolu. Tato nová podpora usnadňuje použití Objective-C idiomů pro přijetí jednoho nebo více protokolů v dané třídě.

Pro každou definici MyProtocol protokolu v Objective-Cnyní existuje IMyProtocol rozhraní, které obsahuje seznam všech požadovaných metod z protokolu, stejně jako rozšiřující třída, která poskytuje všechny volitelné metody. Výše uvedené kombinace s novou podporou v editoru Xamarin Studio umožňuje vývojářům implementovat metody protokolu, aniž by museli používat samostatné podtřídy předchozích abstraktních tříd modelu.

Každá definice, která obsahuje [Protocol] atribut, ve skutečnosti vygeneruje tři podpůrné třídy, které výrazně zlepšují způsob, jakým používáte protokoly:

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

Implementace třídy poskytuje kompletní abstraktní třídu, kterou můžete přepsat jednotlivé metody a získat plnou bezpečnost typů. Vzhledem k tomu, že jazyk C# nepodporuje více dědičnosti, existují scénáře, ve kterých můžete potřebovat mít jinou základní třídu, ale přesto chcete implementovat rozhraní, kde je

Přichází definice vygenerovaného rozhraní. Jedná se o rozhraní, které má všechny požadované metody z protokolu. To umožňuje vývojářům, kteří chtějí implementovat váš protokol, pouze implementovat rozhraní. Modul runtime automaticky zaregistruje typ jako přijetí protokolu.

Všimněte si, že rozhraní uvádí pouze požadované metody a zveřejňuje volitelné metody. To znamená, že třídy, které přijímají protokol, získají úplnou kontrolu typů požadovaných metod, ale budou muset u volitelných metod protokolu použít slabé psaní (ručně pomocí [Export] atributů a shodný s podpisem).

Aby bylo vhodné využívat rozhraní API, které používá protokoly, vytvoří nástroj vazby také třídu metody rozšíření, která zveřejňuje všechny volitelné metody. To znamená, že pokud používáte rozhraní API, budete moct považovat protokoly za všechny metody.

Pokud chcete v rozhraní API použít definice protokolu, budete muset do definice rozhraní API napsat prázdná rozhraní kostry. Pokud chcete použít MyProtocol v rozhraní API, musíte to udělat takto:

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

Výše uvedené informace jsou potřeba, protože v době IMyProtocol vazby neexistuje, proto potřebujete poskytnout prázdné rozhraní.

Přijetí rozhraní generovaných protokolem

Kdykoli implementujete jedno z rozhraní generovaných pro protokoly, například takto:

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

Implementace požadovaných metod rozhraní se exportuje se správným názvem, takže je ekvivalentní tomuto:

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

To bude fungovat pro všechny požadované členy protokolu, ale existuje zvláštní případ s volitelnými selektory, o které je potřeba vědět. Volitelné členy protokolu jsou při použití základní třídy považovány za identické:

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

ale při použití rozhraní protokolu je nutné přidat [Export]. Integrované vývojové prostředí (IDE) ho přidá prostřednictvím automatického dokončování, když ho přidáte s přepsáním.

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

Mezi těmito dvěma za běhu je mírný rozdíl v chování.

  • Uživatelé základní třídy (V příkladu NSUrlSessionDownloadDelegate) poskytují všechny povinné a volitelné selektory, které vracejí rozumné výchozí hodnoty.
  • Uživatelé rozhraní (INSUrlSessionDownloadDelegate v příkladu) reagují pouze na přesné selektory, které jsou k dispozici.

Některé vzácné třídy se zde můžou chovat odlišně. V téměř všech případech je však bezpečné použít jednu z těchto věcí.

Rozšíření třídy vazby

V Objective-C tom je možné rozšířit třídy o nové metody, podobně jako v duchu metody rozšíření jazyka C#. Pokud je k dispozici jedna z těchto metod, můžete použít [BaseType] příznakem metody jako příjemce Objective-C zprávy.

Například v Xamarin.iOS jsme vázali rozšiřující metody definované NSString při UIKit importu jako metody v následujícím příkladu NSStringDrawingExtensions:

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

Seznamy argumentů vazby Objective-C

Objective-C podporuje variadické argumenty. Příklad:

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

Pokud chcete vyvolat tuto metodu z jazyka C#, budete chtít vytvořit podpis takto:

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

Tím se metoda deklaruje jako interní a skryje výše uvedené rozhraní API před uživateli, ale zobrazí se v knihovně. Pak můžete napsat metodu takto:

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

Pole vazby

Někdy budete chtít získat přístup k veřejným polím deklarovaným v knihovně.

Tato pole obvykle obsahují řetězce nebo celočíselné hodnoty, na které se musí odkazovat. Běžně se používají jako řetězec, který představuje konkrétní oznámení a jako klíče ve slovníkech.

Chcete-li svázat pole, přidejte vlastnost do definičního souboru rozhraní a ozdobte vlastnost atributem [Field] . Tento atribut má jeden parametr: název jazyka C symbolu, který se má vyhledat. Příklad:

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

Chcete-li zabalit různá pole do statické třídy, která není odvozena z NSObject, můžete použít [Static] atribut ve třídě, například:

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

Výše uvedená verze vygenerujeLonelyClass, která není odvozena NSObject a bude obsahovat vazbu na vystavenou NSSomeEventNotificationNSString jako .NSString

Atribut [Field] lze použít u následujících datových typů:

  • NSString odkazy (pouze vlastnosti jen pro čtení)
  • NSArray odkazy (pouze vlastnosti jen pro čtení)
  • 32bitové inty (System.Int32)
  • 64bitové inty (System.Int64)
  • 32bitové plovoucí hodnoty (System.Single)
  • 64bitové plovoucí hodnoty (System.Double)
  • System.Drawing.SizeF
  • CGSize

Kromě názvu nativního pole můžete zadat název knihovny, kde se toto pole nachází, předáním názvu knihovny:

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

Pokud propojíte staticky, neexistuje žádná knihovna pro vytvoření vazby __Internal , takže musíte použít název:

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

Výčty vazeb

Přímo do souborů vazeb můžete přidat enum , abyste je mohli snadněji používat v definicích rozhraní API – bez použití jiného zdrojového souboru (který je potřeba zkompilovat jak ve vazbách, tak v konečném projektu).

Příklad:

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

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

Můžete také vytvořit vlastní výčty, které nahradí NSString konstanty. V tomto případě generátor automaticky vytvoří metody pro převod hodnot výčtů a NSString konstanty za vás.

Příklad:

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

V předchozím příkladu byste se mohli rozhodnout ozdobit void Perform (NSString mode); atributem [Internal] . Tím skryjete rozhraní API založené na konstantě před vašimi příjemci vazeb.

To by však omezilo podtřídu typu jako hezčí alternativu [Wrap] rozhraní API používá atribut. Tyto vygenerované metody nejsou virtual, tj. nebudete je moct přepsat – což může, nebo ne, být dobrou volbou.

Alternativou je označení původní NSStringdefinice založené na -based jako [Protected]. To umožní, aby v případě potřeby fungovalo podtřídy a zalamovaná verze bude i nadále fungovat a volat metodu přepsání.

Vazby NSValue, NSNumbera NSString k lepšímu typu

Atribut [BindAs] umožňuje vazbu NSNumberNSValue a NSString(výčty) do přesnějších typů jazyka C#. Atribut lze použít k vytvoření lepšího, přesnějšího rozhraní .NET API přes nativní rozhraní API.

Metody můžete vyzdobit (při návratové hodnotě), parametry a vlastnosti pomocí [BindAs]. Jediným omezením je, že váš člen nesmí být uvnitř [Protocol] nebo [Model] rozhraní.

Příklad:

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

Výstup:

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

Interně provedeme bool?<>NSNumber -a<CGRect ->NSValue převody.

[BindAs] podporuje také pole NSNumberNSValue a NSString(výčty).

Příklad:

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

Výstup:

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

CAScroll je zálohovaný NSString výčet, načteme správnou NSString hodnotu a zpracujeme převod typu.

Informace o podporovaných typech převodů najdete v [BindAs] dokumentaci.

Svázání oznámení

Oznámení jsou zprávy, které se publikují do aplikace NSNotificationCenter.DefaultCenter a slouží jako mechanismus vysílání zpráv z jedné části aplikace do jiné. Vývojáři se přihlašují k odběru oznámení obvykle pomocí metody AddObserver serveru NSNotificationCenter. Když aplikace publikuje zprávu do centra oznámení, obvykle obsahuje datovou část uloženou ve slovníku NSNotification.UserInfo . Tento slovník je slabě napsaný a získání informací z něj je náchylné k chybám, protože uživatelé obvykle potřebují číst v dokumentaci, které klíče jsou dostupné ve slovníku, a typy hodnot, které lze ve slovníku uložit. Přítomnost klíčů se někdy používá také jako logická hodnota.

Generátor vazeb Xamarin.iOS poskytuje vývojářům podporu pro vytvoření vazby oznámení. Uděláte to tak, že nastavíte [Notification] atribut u vlastnosti, která byla také označena značkou [Field] vlastnost (může být veřejná nebo soukromá).

Tento atribut lze použít bez argumentů pro oznámení, která neobsahují datovou část, nebo můžete zadat System.Type odkaz na jiné rozhraní v definici rozhraní API, obvykle s názvem končícím na "EventArgs". Generátor změní rozhraní na třídu, která podtřídy EventArgs a bude obsahovat všechny vlastnosti uvedené tam. Atribut [Export] by měl být použit ve třídě EventArgs k výpisu názvu klíče použitého k vyhledání slovníku Objective-C k načtení hodnoty.

Příklad:

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

Výše uvedený kód vygeneruje vnořenou třídu MyClass.Notifications s následujícími metodami:

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

Uživatelé vašeho kódu se pak můžou snadno přihlásit k odběru oznámení odesílaných do NSDefaultCenter pomocí následujícího kódu:

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

Vrácenou hodnotu lze ObserveDidStart použít k snadnému zastavení přijímání oznámení, například takto:

token.Dispose ();

Nebo můžete volat NSNotification.DefaultCenter.RemoveObserver a předat token. Pokud oznámení obsahuje parametry, měli byste zadat pomocné EventArgs rozhraní, například takto:

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

Výše uvedené vygeneruje MyScreenChangedEventArgs třídu s vlastnostmi ScreenX , ScreenY které načte data ze slovníku NSNotification.UserInfo pomocí klíčů "ScreenXKey" a "ScreenYKey" a použije správné převody. Atribut [ProbePresence] se používá pro generátor pro sondu, pokud je klíč nastaven v UserInfo, místo pokusu o extrakci hodnoty. Používá se v případech, kdy je přítomnost klíče hodnotou (obvykle pro logické hodnoty).

To vám umožní psát kód takto:

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

Kategorie vazeb

Kategorie jsou Objective-C mechanismus, který slouží k rozšíření sady metod a vlastností dostupných ve třídě. V praxi se používají k rozšíření funkcí základní třídy (například NSObject) v případě, že je konkrétní architektura propojena (například UIKit), zpřístupňuje jejich metody, ale pouze v případě, že je nová architektura propojena. V některých jiných případech se používají k uspořádání funkcí ve třídě podle funkcí. Jsou podobné metodám rozšíření jazyka C#. Takto by kategorie vypadala takto Objective-C:

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

Výše uvedený příklad, pokud je nalezen v knihovně, by rozšířil instance UIView s metodou makeBackgroundRed.

K vytvoření vazby těchto atributů můžete použít [Category] atribut v definici rozhraní. Při použití [Category] významu atributu, významu [BaseType] změny atributu, které se používají k určení základní třídy, která se má rozšířit, být typem, který se má rozšířit.

Následující příklad ukazuje, jak UIView jsou rozšíření svázaná a převedená na metody rozšíření jazyka C#:

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

Výše uvedená metoda vytvoří MyUIViewExtension třídu, která obsahuje rozšiřující metodu MakeBackgroundRed . To znamená, že nyní můžete volat "MakeBackgroundRed" u jakékoli UIView podtřídy a poskytnout vám stejné funkce, které byste získali Objective-C. V některých jiných případech se kategorie nepoužívají k rozšíření systémové třídy, ale k uspořádání funkcí čistě pro dekorační účely. Nějak tak:

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

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

I když můžete použít [Category] atribut také pro tento dekorační styl deklarací, můžete také přidat je všechny do definice třídy. Obě tyto možnosti by dosáhly stejného:

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

V těchto případech je sloučení kategorií jen kratší:

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

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

Vazbové bloky

Bloky představují nový konstruktor zavedený společností Apple, který přináší funkční ekvivalent anonymních metod jazyka C# do Objective-C. NSSet Například třída nyní zveřejňuje tuto metodu:

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

Výše uvedený popis deklaruje metodu, enumerateObjectsUsingBlock: která přebírá jeden argument s názvem block. Tento blok se podobá anonymní metodě jazyka C#, protože podporuje zachytávání aktuálního prostředí (ukazatel "tento", přístup k místním proměnným a parametrům). Výše uvedená metoda NSSet vyvolá blok se dvěma parametry NSObject ( id obj část) a ukazatelem na logickou část (část BOOL *stop).

Pokud chcete vytvořit vazbu tohoto typu rozhraní API s btouchem, musíte nejprve deklarovat podpis typu bloku jako delegát jazyka C# a pak na něj odkazovat ze vstupního bodu rozhraní API, například takto:

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

A váš kód teď může volat funkci z jazyka C#:

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

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

Pokud chcete, můžete také použít lambda:

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

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

Asynchronní metody

Generátor vazeb může změnit určitou třídu metod na asynchronní metody (metody, které vracejí úkol nebo úkol<T>).

Můžete použít [Async] atribut pro metody, které vrací void a jehož poslední argument je zpětné volání. Pokud použijete tuto metodu, generátor vazeb vygeneruje verzi této metody s příponou Async. Pokud zpětné volání nepřijímá žádné parametry, návratová hodnota bude Taska , pokud zpětné volání převezme parametr, bude výsledkem Task<T>. Pokud zpětné volání přebírá více parametrů, měli byste nastavit ResultType nebo ResultTypeName zadat požadovaný název vygenerovaného typu, který bude obsahovat všechny vlastnosti.

Příklad:

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

Výše uvedený kód vygeneruje metodu LoadFile i:

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

Zpřístupnění silných typů pro slabé parametry NSDictionary

Na mnoha místech v Objective-C rozhraní API se parametry předávají jako slabě napsaná NSDictionary rozhraní API s konkrétními klíči a hodnotami, ale jsou náchylné k chybám (můžete předat neplatné klíče a nezobrazí se žádná upozornění, můžete předat neplatné hodnoty a získat žádná upozornění) a frustrovat použití, protože vyžadují několik cest do dokumentace k vyhledání možných názvů klíčů a hodnot.

Řešením je poskytnout verzi silného typu, která poskytuje verzi rozhraní API silného typu a na pozadí mapuje různé základní klíče a hodnoty.

Pokud například Objective-C rozhraní API přijalo NSDictionary a je zdokumentované jako klíč, který přebírá NSNumber hodnotu XyzVolumeKey svazku od 0.0 do 1.0 a řetězecXyzCaptionKey, měli byste chtít, aby vaši uživatelé měli pěkné rozhraní API, které vypadá takto:

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

Vlastnost Volume je definována jako float s možnou hodnotou null, protože konvence nevyžaduje Objective-C , aby tyto slovníky měly hodnotu, takže existují scénáře, kdy hodnota nemusí být nastavena.

K tomu je potřeba udělat několik věcí:

  • Vytvořte třídu silného typu, která podtřídy DictionaryContainer a poskytuje různé metody getter a setter pro každou vlastnost.
  • Deklarujte přetížení pro metody, které přebírají NSDictionary novou verzi silného typu.

Třídu silného typu můžete vytvořit buď ručně, nebo pomocí generátoru provést práci za vás. Nejprve prozkoumáme, jak to udělat ručně, abyste pochopili, co se děje, a pak automatický přístup.

Pro tento postup potřebujete vytvořit podpůrný soubor, který nepřejde do rozhraní API pro kontrakty. To je to, co byste museli napsat k vytvoření třídy XyzOptions:

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

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

Pak byste měli poskytnout metodu obálky, která zobrazí rozhraní API vysoké úrovně nad rozhraním API nízké úrovně.

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

Pokud vaše rozhraní API nemusí být přepsáno, můžete bezpečně skrýt rozhraní API založené na NSDictionary pomocí Atribut [Internal].

Jak vidíte, používáme [Wrap] k vytvoření nového vstupního bodu rozhraní API a my ho zobrazíme pomocí naší třídy silného typu XyzOptions . Metoda wrapper také umožňuje předání hodnoty null.

Jedna věc, kterou jsme nezmínili, je místo, odkud XyzOptionsKeys hodnoty pocházejí. Obvykle byste seskupily klíče, které rozhraní API zobrazí ve statické třídě, například XyzOptionsKeystakto:

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

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

Podívejme se na automatickou podporu vytváření těchto slovníků se silnými typy. Vyhnete se tak spoustě často používaného slovníku a slovník můžete definovat přímo ve smlouvě rozhraní API místo použití externího souboru.

Pokud chcete vytvořit slovník silného typu, zaveďte rozhraní ve vašem rozhraní API a ozdobte ho atributem StrongDictionary . To říká generátoru, že by měl vytvořit třídu se stejným názvem jako vaše rozhraní, které bude odvozeno a DictionaryContainer bude poskytovat silné typové přístupové objekty.

Atribut [StrongDictionary] přebírá jeden parametr, což je název statické třídy, která obsahuje klíče slovníku. Pak se každá vlastnost rozhraní stane přístupovým objektem silného typu. Ve výchozím nastavení kód použije název vlastnosti s příponou "Klíč" ve statické třídě k vytvoření přístupového objektu.

To znamená, že vytvoření přístupového objektu silného typu už nevyžaduje externí soubor, ani nemusíte ručně vytvářet metody getter a setter pro každou vlastnost, ani nemusíte klíče hledat ručně.

Vaše celá vazba by vypadala takto:

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

V případě, že potřebujete odkazovat ve svých XyzOption členech na jiné pole (to není název vlastnosti s příponou Key), můžete vlastnost ozdobit pomocí [Export] atribut s názvem, který chcete použít.

Mapování typů

Tato část popisuje, jak Objective-C se typy mapují na typy jazyka C#.

Jednoduché typy

Následující tabulka ukazuje, jak byste měli mapovat typy z Objective-C světa CocoaTouch na svět Xamarin.iOS:

Objective-C název typu Typ sjednoceného rozhraní API Xamarin.iOS
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString (další informace o vazbě NSString) string
char * string (viz také: [PlainString])
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
Typy CoreFoundation (CF*) CoreFoundation.CF*
GLint nint
GLfloat nfloat
Základní typy (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

Pole

Modul runtime Xamarin.iOS se automaticky postará o převod polí NSArrays jazyka C# a provádí převod zpět, takže například imaginární Objective-C metoda, která vrací NSArray :UIViews

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

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

Je vázán takto:

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

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

Cílem je použít pole C# silného typu, protože to umožní integrovanému vývojovému prostředí (IDE) poskytnout správné dokončování kódu skutečným typem bez vynucení odhadu uživatele nebo vyhledáním dokumentace k nalezení skutečného typu objektů obsažených v poli.

V případech, kdy nemůžete sledovat skutečný odvozený typ obsažený v matici, můžete použít NSObject [] jako návratovou hodnotu.

Voliče

Selektory se zobrazí v Objective-C rozhraní API jako speciální typ SEL. Při vytváření vazby selektoru byste typ namapoval na ObjCRuntime.Selector. Selektory se obvykle zveřejňují v rozhraní API s objektem, cílovým objektem a selektorem, který se má vyvolat v cílovém objektu. Poskytnutí obou těchto v podstatě odpovídá delegátu jazyka C#: něco, co zapouzdřuje metodu vyvolání i objekt pro vyvolání metody.

Tato vazba vypadá takto:

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

A takto se metoda obvykle používá v aplikaci:

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

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

Aby byla vazba pro vývojáře v jazyce C# hezčí, obvykle poskytnete metodu NSAction , která přebírá parametr, který umožňuje používat delegáty jazyka Target+SelectorC# a lambda místo . K tomu obvykle skryjete metodu SetTarget tím, že ji označíte příznakem [Internal] a pak byste zpřístupnili novou pomocnou metodu, například takto:

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

Takže teď můžete kód uživatele napsat takto:

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

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

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

Řetězce

Když vytváříte vazbu metody, která přebírá NSString, můžete ji nahradit typem řetězce jazyka C#, a to jak u návratových typů, tak parametrů.

Jediný případ, kdy byste mohli chtít použít NSString přímo, je, když se řetězec použije jako token. Další informace o řetězcích a NSStringpřečtěte si o návrhu rozhraní API v dokumentu NSString .

V některýchvýjimečnýchchar *Objective-CNSString * V těchto případech můžete parametr anotovat pomocí Atribut [PlainString].

parametry out/ref

Některá rozhraní API vrací hodnoty v parametrech nebo předávají parametry odkazem.

Podpis obvykle vypadá takto:

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

První příklad ukazuje běžný Objective-C idiom pro vrácení kódů chyb, ukazatel na NSError ukazatel je předán a po vrácení hodnoty je nastavena. Druhá metoda ukazuje, jak Objective-C může metoda vzít objekt a změnit jeho obsah. Jedná se o předávací odkaz, nikoli čistou výstupní hodnotu.

Vaše vazba by vypadala takto:

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

Atributy správy paměti

Pokud použijete [Export] atribut a předáváte data, která budou zachována volánou metodou, můžete určit sémantiku argumentu tak, že je předáte jako druhý parametr, například:

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

Výše uvedená hodnota by označovala jako sémantiku "Zachovat". K dispozici jsou sémantika:

  • Přiřadit
  • Kopírovat
  • Zachovat

Pokyny pro styly

Použití [Interní]

Můžete použít [Internal] atribut pro skrytí metody z veřejného rozhraní API. Můžete to udělat v případech, kdy je vystavené rozhraní API příliš nízké a chcete poskytnout implementaci vysoké úrovně v samostatném souboru založeném na této metodě.

Můžete ho použít také v případě, že v generátoru vazeb narazíte na omezení, například některé pokročilé scénáře můžou vystavit typy, které nejsou svázané a chcete je svázat vlastním způsobem, a chcete tyto typy zabalit vlastním způsobem.

Obslužné rutiny událostí a zpětná volání

Objective-C třídy obvykle vysílat oznámení nebo požadovat informace odesláním zprávy na delegát třídy (Objective-C delegát).

Tento model může být někdy těžkopádný a plně podporovaný a povrchovaný Xamarin.iOS. Xamarin.iOS zveřejňuje vzor událostí jazyka C# a systém zpětného volání metody ve třídě, kterou lze v těchto situacích použít. Tento postup umožňuje spuštění kódu:

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

Generátor vazeb dokáže snížit množství psaní potřebné k namapování Objective-C vzoru na vzor jazyka C#.

Počínaje Xamarin.iOS 1.4 bude možné generátoru také instruovat, aby vytvořil vazby pro konkrétní Objective-C delegáty a zpřístupnil delegáta jako události a vlastnosti jazyka C# v typu hostitele.

V tomto procesu jsou zapojeny dvě třídy, třída hostitele, která bude v současné době generovat události a odesílá je do Delegate nebo WeakDelegate a skutečné delegovat třídy.

Zvažte následující nastavení:

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

Chcete-li zabalit třídu, musíte:

  • V hostitelské třídě přidejte do třídy svého hostitele [BaseType]
    deklarací typu, který funguje jako jeho delegát, a název jazyka C#, který jste odhalili. V našem příkladu výše jsou typeof (MyClassDelegate) a WeakDelegate v uvedeném pořadí.
  • Ve třídě delegáta musíte pro každou metodu, která má více než dva parametry, zadat typ, který chcete použít pro automaticky vygenerovanou třídu EventArgs.

Generátor vazeb není omezen pouze na zabalení jednoho cíle události, je možné, že některé Objective-C třídy generovat zprávy více než jednomu delegátu, takže budete muset poskytnout pole pro podporu tohoto nastavení. Většina nastavení ho nebude potřebovat, ale generátor je připravený na podporu těchto případů.

Výsledný kód bude:

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

Slouží EventArgs k určení názvu EventArgs třídy, která se má vygenerovat. Měli byste použít jeden podpis (v tomto příkladu EventArgs bude obsahovat With vlastnost typu nint).

S výše uvedenými definicemi generátor vygeneruje ve vygenerované třídě MyClass následující událost:

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

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

Teď můžete kód použít takto:

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

Zpětná volání jsou stejně jako vyvolání událostí, rozdíl spočívá v tom, že místo toho, aby bylo možné připojit více potenciálních odběratelů (například více metod může připojit k Clicked události nebo DownloadFinished události), můžou mít zpětné volání pouze jednoho odběratele.

Proces je stejný, jediným rozdílem je, že místo vystavení názvu EventArgs třídy, která se vygeneruje, se eventArgs ve skutečnosti používá k pojmenování výsledného názvu delegáta jazyka C#.

Pokud metoda ve třídě delegáta vrátí hodnotu, generátor vazeb namapuje tuto metodu na metodu delegáta v nadřazené třídě místo události. V těchto případech musíte zadat výchozí hodnotu, která by měla být vrácena metodou, pokud uživatel nepřipojí delegáta. Provedete to pomocí [DefaultValue] nebo [DefaultValueFromArgument] atributy.

[DefaultValue] pevně zakóduje návratovou hodnotu, zatímco [DefaultValueFromArgument] slouží k určení, který vstupní argument bude vrácen.

Výčty a základní typy

Můžete také odkazovat na výčty nebo základní typy, které nejsou přímo podporovány definičním systémem rozhraní btouch. Uděláte to tak, že vložíte výčty a základní typy do samostatného souboru a zahrnete ho jako součást jednoho z dalších souborů, které zadáte pro btouch.

Propojení závislostí

Pokud vytváříte vazby rozhraní API, která nejsou součástí vaší aplikace, musíte se ujistit, že je váš spustitelný soubor propojený s těmito knihovnami.

Potřebujete informovat Xamarin.iOS, jak propojit knihovny. Můžete to udělat buď změnou konfigurace sestavení a vyvolat mtouch příkaz s některými dalšími argumenty sestavení, které určují, jak propojit s novými knihovnami pomocí možnosti -gcc_flags, následovaný řetězcem s uvozováním obsahujícím všechny další knihovny, které jsou pro váš program potřeba, Nějak tak:

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

Výše uvedený příklad bude odkazovat libMyLibrary.alibSystemLibrary.dylib a CFNetwork knihovna architektury do konečného spustitelného souboru.

Nebo můžete využít výhod úrovně [LinkWithAttribute]sestavení , které můžete vložit do souborů kontraktů (například AssemblyInfo.cs). Když použijete tuto [LinkWithAttribute]možnost , budete muset mít svou nativní knihovnu k dispozici v době, kdy vytvoříte vazbu, protože tím se vloží nativní knihovna s vaší aplikací. Příklad:

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

Možná vás zajímá, proč potřebujete -force_load příkaz, a důvodem je, že příznak -ObjC, i když kód zkompiluje, nezachovává metadata potřebná k podpoře kategorií (linker/kompilátor ho odstraní mrtvý kód), který potřebujete za běhu pro Xamarin.iOS.

Asistované reference

Některé přechodné objekty, jako jsou listy akcí a pole upozornění, jsou těžkopádné, aby vývojáři sledovali a generátor vazeb může pomoct trochu tady.

Pokud jste například měli třídu, která zobrazila zprávu a pak vygenerovala Done událost, tradiční způsob zpracování by byl:

class Demo {
    MessageBox box;

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

Ve výše uvedeném scénáři musí vývojář zachovat odkaz na objekt sám a buď únik, nebo aktivně vymazat odkaz na pole sám. Zatímco vazební kód, generátor podporuje sledování odkazu pro vás a vymazat jej při vyvolání speciální metody, výše uvedený kód by se pak stal:

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

Všimněte si, že už není nutné zachovat proměnnou v instanci, že funguje s místní proměnnou a že není nutné vymazat odkaz, když objekt zemře.

Chcete-li využít tuto výhodu, třída by měla mít vlastnost Events nastavena v [BaseType] deklaraci a také KeepUntilRef proměnná nastavena na název metody, která je vyvolána při dokončení objektu jeho práce, jako je tento:

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

Dědění protokolů

Od verze Xamarin.iOS v3.2 podporujeme dědění z protokolů označených vlastností [Model] . To je užitečné v určitých vzorech rozhraní API, například v MapKit místě, kde MKOverlay protokol dědí z MKAnnotation protokolu, a je přijat řadou tříd, které dědí z NSObject.

V minulosti jsme vyžadovali kopírování protokolu do každé implementace, ale v těchto případech teď můžeme mít MKShape třídu dědit z MKOverlay protokolu a vygeneruje všechny požadované metody automaticky.