Udostępnij za pośrednictwem


Tworzenie powiązań Objective-C bibliotek

Podczas pracy z platformą Xamarin.iOS lub Xamarin.Mac mogą wystąpić przypadki, w których chcesz korzystać z biblioteki innej firmy Objective-C . W takich sytuacjach można użyć projektów powiązań platformy Xamarin, aby utworzyć powiązanie języka C# z bibliotekami natywnymi Objective-C . Projekt używa tych samych narzędzi, których używamy do dostarczania interfejsów API systemów iOS i Mac do języka C#.

W tym dokumencie opisano sposób wiązania Objective-C interfejsów API, jeśli są powiązane tylko interfejsy API języka C, należy użyć standardowego mechanizmu .NET w tym celu, struktury P/Invoke. Szczegółowe informacje na temat statycznego łączenia biblioteki języka C są dostępne na stronie Łączenie bibliotek natywnych.

Zapoznaj się z naszym przewodnikiem referencyjnym dotyczącym typów powiązań towarzyszących. Ponadto jeśli chcesz dowiedzieć się więcej o tym, co dzieje się pod maską, sprawdź naszą stronę Przegląd powiązań.

Powiązania można tworzyć zarówno dla bibliotek systemów iOS, jak i Mac. Na tej stronie opisano sposób pracy z powiązaniem systemu iOS, jednak powiązania dla komputerów Mac są bardzo podobne.

Przykładowy kod dla systemu iOS

Przykładowy projekt powiązania systemu iOS umożliwia eksperymentowanie z powiązaniami.

Wprowadzenie

Najprostszym sposobem utworzenia powiązania jest utworzenie projektu powiązania platformy Xamarin.iOS. Możesz to zrobić w Visual Studio dla komputerów Mac, wybierając typ projektu, bibliotekę powiązań biblioteki >systemu iOS>:

Wykonaj to z Visual Studio dla komputerów Mac, wybierając typ projektu, bibliotekę powiązań biblioteki systemu iOS

Wygenerowany projekt zawiera mały szablon, który można edytować, zawiera dwa pliki: ApiDefinition.cs i StructsAndEnums.cs.

W tym miejscu zdefiniujesz kontrakt interfejsu API. Jest ApiDefinition.cs to plik opisujący sposób projekcji bazowego Objective-C interfejsu API w języku C#. Składnia i zawartość tego pliku są głównym tematem dyskusji na temat tego dokumentu, a jego zawartość jest ograniczona do interfejsów języka C# i deklaracji delegatów języka C#. Plik StructsAndEnums.cs jest plikiem, w którym należy wprowadzić wszelkie definicje wymagane przez interfejsy i delegaty. Obejmuje to wartości wyliczenia i struktury, których może używać twój kod.

Wiązanie interfejsu API

Aby wykonać kompleksowe powiązanie, warto zapoznać się z definicją interfejsu Objective-C API i zapoznać się z wytycznymi dotyczącymi projektowania programu .NET Framework.

Aby powiązać bibliotekę, zazwyczaj rozpoczniesz od pliku definicji interfejsu API. Plik definicji interfejsu API jest tylko plikiem źródłowym języka C#, który zawiera interfejsy języka C#, które zostały oznaczone za pomocą kilku atrybutów, które ułatwiają wspieranie powiązania. Ten plik definiuje, jaki jest kontrakt między C# i Objective-C jest.

Na przykład jest to prosty plik interfejsu API dla biblioteki:

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

Powyższy przykład definiuje klasę o nazwie Cocos2D.Camera , która pochodzi z typu podstawowego NSObject (ten typ pochodzi z Foundation.NSObject) i która definiuje właściwość statyczną (ZEye), dwie metody, które nie przyjmują żadnych argumentów, i metodę, która przyjmuje trzy argumenty.

Szczegółowe omówienie formatu pliku interfejsu API i atrybutów, których można użyć, znajduje się w poniższej sekcji pliku definicji interfejsu API.

Aby utworzyć pełne powiązanie, zazwyczaj zajmujesz się czterema składnikami:

  • Plik definicji interfejsu API (ApiDefinition.cs w szablonie).
  • Opcjonalnie: wszelkie wyliczenia, typy, struktury wymagane przez plik definicji interfejsu API (StructsAndEnums.cs w szablonie).
  • Opcjonalnie: dodatkowe źródła, które mogą rozwinąć wygenerowane powiązanie lub udostępnić bardziej przyjazny interfejs API języka C# (wszystkie pliki języka C#dodawane do projektu).
  • Biblioteka natywna, którą wiążesz.

Ten wykres przedstawia relację między plikami:

Ten wykres przedstawia relację między plikami

Plik definicji interfejsu API będzie zawierać tylko przestrzenie nazw i definicje interfejsu (z dowolnymi elementami członkowskimi, które mogą zawierać interfejs) i nie powinny zawierać klas, wyliczenia, delegatów ani struktur. Plik definicji interfejsu API to tylko kontrakt, który będzie używany do generowania interfejsu API.

Każdy dodatkowy kod, którego potrzebujesz, takich jak wyliczenia lub klasy pomocnicze, powinny być hostowane w osobnym pliku, w powyższym przykładzie wartość "Aparat Mode" jest wartością wyliczenia, która nie istnieje w pliku CS i powinna być hostowana w osobnym pliku, na przykład StructsAndEnums.cs:

public enum CameraMode {
    FlyOver, Back, Follow
}

Plik APIDefinition.cs jest połączony z klasą StructsAndEnum i służy do generowania podstawowego powiązania biblioteki. Możesz użyć wynikowej biblioteki w taki sposób, jak to jest, ale zazwyczaj chcesz dostroić wynikową bibliotekę, aby dodać niektóre funkcje języka C# z korzyścią dla użytkowników. Niektóre przykłady obejmują implementowanie ToString() metody, udostępnianie indeksatorów języka C#, dodawanie niejawnych konwersji do i z niektórych typów natywnych lub udostępnianie silnie typicznych wersji niektórych metod. Te ulepszenia są przechowywane w dodatkowych plikach języka C#. Wystarczy dodać pliki języka C# do projektu i zostaną one uwzględnione w tym procesie kompilacji.

Pokazuje to, jak zaimplementować kod w Extra.cs pliku. Zwróć uwagę, że będziesz używać klas częściowych, ponieważ te rozszerzają klasy częściowe generowane na podstawie kombinacji powiązania podstawowego ApiDefinition.csStructsAndEnums.cs i :

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

Kompilowanie biblioteki spowoduje utworzenie powiązania natywnego.

Aby ukończyć to powiązanie, należy dodać bibliotekę natywną do projektu. Możesz to zrobić, dodając bibliotekę natywną do projektu, przeciągając i upuszczając bibliotekę natywną z programu Finder do projektu w Eksploratorze rozwiązań lub klikając projekt prawym przyciskiem myszy i wybierając polecenie Dodaj>pliki, aby wybrać bibliotekę natywną. Biblioteki natywne zgodnie z konwencją zaczynają się od słowa "lib" i kończą się rozszerzeniem ".a". Gdy to zrobisz, Visual Studio dla komputerów Mac doda dwa pliki: plik .a i automatycznie wypełniony plik C#, który zawiera informacje o tym, co zawiera biblioteka natywna:

Biblioteki natywne według konwencji zaczynają się od biblioteki słów i kończą się rozszerzeniem .a

libMagicChord.linkwith.cs Zawartość pliku zawiera informacje o sposobie użycia tej biblioteki i instruuje środowisko IDE, aby spakować ten plik binarny do wynikowego pliku DLL:

using System;
using ObjCRuntime;

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

Pełne informacje o sposobie korzystania z [LinkWith]atrybut jest udokumentowany w Przewodniku referencyjnym typów powiązań.

Teraz po utworzeniu projektu zostanie wyświetlony MagicChords.dll plik zawierający zarówno powiązanie, jak i bibliotekę natywną. Ten projekt lub wynikowa biblioteka DLL można dystrybuować do innych deweloperów na własny użytek.

Czasami może się okazać, że potrzebujesz kilku wartości wyliczenia, definicji delegatów lub innych typów. Nie umieszczaj tych w pliku definicji interfejsu API, ponieważ jest to tylko kontrakt

Plik definicji interfejsu API

Plik definicji interfejsu API składa się z wielu interfejsów. Interfejsy w definicji interfejsu API zostaną przekształcone w deklarację klasy i muszą zostać ozdobione atrybutem [BaseType] w celu określenia klasy bazowej dla klasy.

Być może zastanawiasz się, dlaczego nie używaliśmy klas zamiast interfejsów dla definicji kontraktu. Wybraliśmy interfejsy, ponieważ pozwoliło nam napisać kontrakt dla metody bez konieczności podawania treści metody w pliku definicji interfejsu API lub konieczności podania treści, która musiała zgłosić wyjątek lub zwrócić znaczącą wartość.

Ponieważ jednak używamy interfejsu jako szkieletu, aby wygenerować klasę, musieliśmy uciekać się do dekorowania różnych części kontraktu z atrybutami, aby napędzać powiązanie.

Metody wiązania

Najprostszym powiązaniem, które można zrobić, jest powiązanie metody. Po prostu zadeklaruj metodę w interfejsie za pomocą konwencji nazewnictwa języka C# i udekoruj metodę za pomocą metody Atrybut [Export]. Atrybutem [Export] jest to, co łączy nazwę języka C# z Objective-C nazwą w środowisku uruchomieniowym platformy Xamarin.iOS. Parametr parametru [Export] atrybut jest nazwą selektora Objective-C . Kilka przykładów:

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

W powyższych przykładach pokazano, jak można powiązać metody wystąpienia. Aby powiązać metody statyczne, należy użyć atrybutu [Static] w następujący sposób:

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

Jest to wymagane, ponieważ kontrakt jest częścią interfejsu, a interfejsy nie mają pojęcia deklaracji statycznych i wystąpień, dlatego konieczne jest po raz kolejny uciekanie się do atrybutów. Jeśli chcesz ukryć określoną metodę przed powiązaniem, możesz udekorować metodę za pomocą atrybutu [Internal] .

Polecenie btouch-native wprowadzi kontrole parametrów referencyjnych, aby nie mieć wartości null. Jeśli chcesz zezwolić na wartości null dla określonego parametru, użyj [NullAllowed] atrybut parametru, w następujący sposób:

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

Podczas eksportowania typu odwołania za pomocą [Export] słowa kluczowego można również określić semantyka alokacji. Jest to konieczne, aby upewnić się, że żadne dane nie wyciekają.

Właściwości powiązań

Podobnie jak metody, Objective-C właściwości są powiązane przy użyciu [Export] atrybut i mapuj bezpośrednio na właściwości języka C#. Podobnie jak metody, właściwości można ozdobić za pomocą [Static] i [Internal] Atrybuty.

Jeśli używasz atrybutu [Export] we właściwości w obszarze obejmuje btouch-native faktycznie wiąże dwie metody: getter i setter. Nazwa podana do wyeksportowania to nazwa bazowa, a element ustawiający jest obliczany przez poprzedzanie słowa "set", zamieniając pierwszą literę nazwy bazowej na wielkie litery i tworząc selektor argumentu. Oznacza to, że [Export ("label")] zastosowanie właściwości rzeczywiście wiąże metody "label" i "setLabel:". Objective-C

Objective-C Czasami właściwości nie są zgodne ze wzorcem opisanym powyżej, a nazwa jest ręcznie zastępowana. W takich przypadkach można kontrolować sposób generowania powiązania przy użyciu elementu [Bind] atrybut w metodzie getter lub setter, na przykład:

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

Następnie wiąże element "isMenuVisible" i "setMenuVisible:". Opcjonalnie właściwość można powiązać przy użyciu następującej składni:

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

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

Gdzie element getter i setter są jawnie zdefiniowane tak, jak w name powyższych powiązaniach i setName .

Oprócz obsługi właściwości statycznych za pomocą polecenia [Static]można dekorować właściwości statyczne wątku za pomocą [IsThreadStatic]polecenia , na przykład:

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

Podobnie jak metody umożliwiają oflagowane niektóre parametry za pomocą [NullAllowed]polecenia , można zastosować [NullAllowed] do właściwości wskazującej, że wartość null jest prawidłową wartością właściwości, na przykład:

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

Parametr [NullAllowed] można również określić bezpośrednio w ustawieniu:

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

Zastrzeżenia dotyczące powiązań kontrolek niestandardowych

Podczas konfigurowania powiązania dla kontrolki niestandardowej należy rozważyć następujące zastrzeżenia:

  1. Właściwości powiązania muszą być statyczne — podczas definiowania powiązania właściwości należy użyć atrybutu [Static] .
  2. Nazwy właściwości muszą być dokładnie zgodne — nazwa używana do powiązania właściwości musi dokładnie odpowiadać nazwie właściwości w kontrolce niestandardowej.
  3. Typy właściwości muszą być dokładnie zgodne — typ zmiennej używany do powiązania właściwości musi być dokładnie zgodny z typem właściwości w kontrolce niestandardowej.
  4. Punkty przerwania i getter/setter — punkty przerwania umieszczone w metodach getter lub setter właściwości nigdy nie zostaną trafione.
  5. Obserwowanie wywołań zwrotnych — należy użyć wywołań zwrotnych obserwacji, aby otrzymywać powiadomienia o zmianach w wartościach właściwości kontrolek niestandardowych.

Niepowodzenie obserwowania któregokolwiek z powyższych zastrzeżeń może spowodować dyskretne niepowodzenie powiązania w czasie wykonywania.

Objective-C modyfikowalny wzorzec i właściwości

Objective-C struktury używają idiomu, w którym niektóre klasy są niezmienne z podklasą mutable. Na przykład NSString jest niezmienną wersją, natomiast NSMutableString jest podklasą, która umożliwia mutację.

W tych klasach często widać niezmienną klasę bazową zawierającą właściwości z metodą getter, ale bez ustawiania. I dla wersji modyfikowalnej, aby wprowadzić setter. Ponieważ nie jest to naprawdę możliwe w języku C#, musieliśmy mapować ten idiom na idiom, który będzie działać z językiem C#.

Sposób, w jaki jest on mapowany na język C#, polega na dodaniu zarówno metody getter, jak i metody ustawiającej w klasie bazowej, ale flagując element setter z elementem Atrybut [NotImplemented].

Następnie w podklasie modyfikowalnej użyj polecenia [Override] atrybut właściwości , aby upewnić się, że właściwość rzeczywiście zastępuje zachowanie elementu nadrzędnego.

Przykład:

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

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

Konstruktory powiązań

Narzędzie btouch-native automatycznie wygeneruje konstruktory czwórki w klasie dla danej klasy Foo, które generuje:

  • Foo (): konstruktor domyślny (mapuje na Objective-Ckonstruktor "init")
  • Foo (NSCoder): konstruktor używany podczas deserializacji plików NIB (mapuje na Objective-Ckonstruktor "initWithCoder:").
  • Foo (IntPtr handle): konstruktor do tworzenia opartego na obsłudze, jest wywoływany przez środowisko uruchomieniowe, gdy środowisko uruchomieniowe musi uwidocznić obiekt zarządzany z niezarządzanego obiektu.
  • Foo (NSEmptyFlag): jest to używane przez klasy pochodne, aby zapobiec podwójnej inicjowaniu.

W przypadku zdefiniowanych konstruktorów należy je zadeklarować przy użyciu następującego podpisu w definicji interfejsu: muszą zwrócić IntPtr wartość, a nazwa metody powinna być konstruktorem. Na przykład w celu powiązania konstruktora initWithFrame: jest to, czego należy użyć:

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

Powiązania protokołów

Zgodnie z opisem w dokumencie projektowania interfejsu API w sekcji omawiania modeli i protokołów platforma Xamarin.iOS mapuje Objective-C protokoły na klasy oflagowane za pomocą elementu Atrybut [Model]. Jest to zwykle używane podczas implementowania Objective-C klas delegatów.

Wielką różnicą między regularną klasą powiązana a klasą delegata jest to, że klasa delegata może mieć co najmniej jedną opcjonalną metodę.

Rozważmy na przykład klasę UIKitUIAccelerometerDelegate, w jaki sposób jest ona powiązana w środowisku Xamarin.iOS:

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

Ponieważ jest to opcjonalna metoda w definicji , nie UIAccelerometerDelegate ma nic innego do zrobienia. Ale jeśli w protokole była wymagana metoda, należy dodać [Abstract] atrybut metody . Spowoduje to wymusić, że użytkownik implementacji faktycznie udostępni treść metody.

Ogólnie rzecz biorąc, protokoły są używane w klasach, które reagują na komunikaty. Zazwyczaj odbywa się Objective-C to przez przypisanie do właściwości "delegate" wystąpienia obiektu, które odpowiada na metody w protokole.

Konwencją w środowisku Xamarin.iOS jest obsługa zarówno luźno powiązanego Objective-C stylu, w którym można przypisać dowolne wystąpienie obiektu NSObject do delegata, a także uwidocznić silnie typizowana wersję. Z tego powodu zazwyczaj udostępniamy zarówno Delegate właściwość, która jest silnie typizowana, jak WeakDelegate i luźno typizowana. Zazwyczaj wiążemy luźno typizowana wersję z elementem [Export]i używamy atrybutu [Wrap] w celu zapewnienia silnie typizowanej wersji.

Pokazuje to, jak powiązaliśmy klasę UIAccelerometer :

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

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

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

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

Nowość w aplikacji MonoTouch 7.0

Począwszy od aplikacji MonoTouch 7.0 włączono nową i ulepszoną funkcję powiązania protokołu. Ta nowa obsługa ułatwia używanie Objective-C idiomów do wdrażania co najmniej jednego protokołu w danej klasie.

Dla każdej definicji MyProtocol protokołu w systemie Objective-Cistnieje teraz IMyProtocol interfejs, który zawiera listę wszystkich wymaganych metod z protokołu, a także klasę rozszerzenia, która udostępnia wszystkie metody opcjonalne. Powyższe, w połączeniu z nową obsługą w edytorze Xamarin Studio, umożliwiają deweloperom implementowanie metod protokołu bez konieczności używania oddzielnych podklas poprzednich klas modelu abstrakcyjnego.

Każda definicja zawierająca [Protocol] atrybut faktycznie wygeneruje trzy klasy pomocnicze, które znacznie poprawią sposób korzystania z protokołów:

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

Implementacja klasy zapewnia kompletną abstrakcyjną klasę, którą można zastąpić poszczególnymi metodami i uzyskać bezpieczeństwo pełnego typu. Jednak ze względu na to, że język C# nie obsługuje wielu dziedziczenia, istnieją scenariusze, w których może być konieczne posiadanie innej klasy bazowej, ale nadal chcesz zaimplementować interfejs, w tym miejscu

Zostanie wygenerowana definicja interfejsu. Jest to interfejs, który ma wszystkie wymagane metody z protokołu. Dzięki temu deweloperzy, którzy chcą zaimplementować protokół tylko zaimplementować interfejs. Środowisko uruchomieniowe automatycznie zarejestruje typ jako przyjmujący protokół.

Zwróć uwagę, że interfejs wyświetla tylko wymagane metody i udostępnia opcjonalne metody. Oznacza to, że klasy, które przyjmują protokół, uzyskają pełne sprawdzanie typów dla wymaganych metod, ale będą musiały uciekać się do słabego wpisywania (ręcznie przy użyciu [Export] atrybutów i dopasowywania podpisu) dla opcjonalnych metod protokołu.

Aby ułatwić korzystanie z interfejsu API korzystającego z protokołów, narzędzie powiązania utworzy również klasę metody rozszerzeń, która uwidacznia wszystkie metody opcjonalne. Oznacza to, że o ile korzystasz z interfejsu API, będziesz w stanie traktować protokoły jako wszystkie metody.

Jeśli chcesz użyć definicji protokołu w interfejsie API, musisz napisać szkieletowe puste interfejsy w definicji interfejsu API. Jeśli chcesz użyć aplikacji MyProtocol w interfejsie API, musisz wykonać następujące czynności:

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

Powyższe elementy są potrzebne, ponieważ w momencie IMyProtocol powiązania obiekt nie istnieje, dlatego należy podać pusty interfejs.

Wdrażanie interfejsów generowanych przez protokół

Za każdym razem, gdy implementujesz jeden z interfejsów generowanych dla protokołów, w następujący sposób:

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

Implementacja wymaganych metod interfejsu jest eksportowana z odpowiednią nazwą, więc jest ona równoważna następującemu:

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

Będzie to działać dla wszystkich wymaganych elementów członkowskich protokołu, ale istnieje szczególny przypadek z opcjonalnymi selektorami, o których należy pamiętać. Opcjonalne elementy członkowskie protokołu są traktowane identycznie podczas korzystania z klasy bazowej:

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

ale w przypadku korzystania z interfejsu protokołu jest wymagany do dodania [Eksportuj]. Środowisko IDE doda je za pośrednictwem autouzupełniania po dodaniu, rozpoczynając od zastąpienia.

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

Istnieje niewielka różnica między nimi w czasie wykonywania.

  • Użytkownicy klasy bazowej (NSUrlSessionDownloadDelegate w przykładzie) udostępniają wszystkie wymagane i opcjonalne selektory, zwracając rozsądne wartości domyślne.
  • Użytkownicy interfejsu (INSUrlSessionDownloadDelegate w przykładzie) reagują tylko na podane selektory.

Niektóre rzadkie klasy mogą zachowywać się inaczej tutaj. W prawie wszystkich przypadkach można jednak bezpiecznie użyć.

Powiązania rozszerzeń klas

W Objective-C programie można rozszerzyć klasy na nowe metody, podobnie jak metody rozszerzeń języka C#. Gdy jedna z tych metod jest obecna, można użyć [BaseType] atrybut flagi metody jako odbiornik komunikatu Objective-C .

Na przykład w środowisku Xamarin.iOS powiązaliśmy metody rozszerzenia zdefiniowane podczas NSStringUIKit importowania jako metody w NSStringDrawingExtensionspliku , w następujący sposób:

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

Tworzenie powiązań Objective-C list argumentów

Objective-C obsługuje argumenty wariadyczne. Na przykład:

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

Aby wywołać tę metodę z poziomu języka C#, należy utworzyć podpis podobny do następującego:

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

To deklaruje metodę jako wewnętrzną, ukrywając powyższy interfejs API przed użytkownikami, ale uwidaczniając ją w bibliotece. Następnie możesz napisać metodę podobną do następującej:

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

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

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

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

Pola powiązania

Czasami należy uzyskać dostęp do pól publicznych zadeklarowanych w bibliotece.

Zazwyczaj te pola zawierają ciągi lub wartości całkowite, do których należy się odwoływać. Są one często używane jako ciąg reprezentujący określone powiadomienie i jako klucze w słownikach.

Aby powiązać pole, dodaj właściwość do pliku definicji interfejsu i udekoruj właściwość za pomocą atrybutu [Field] . Ten atrybut przyjmuje jeden parametr: nazwę C symbolu do wyszukania. Na przykład:

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

Jeśli chcesz opakowować różne pola w klasie statycznej, która nie pochodzi z NSObjectklasy , możesz użyć polecenia [Static] atrybut w klasie, w następujący sposób:

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

Powyższe wartości wygenerują element LonelyClass , który nie pochodzi z NSObject elementu i będzie zawierać powiązanie z NSSomeEventNotificationNSString uwidocznione jako NSString.

Atrybut [Field] można zastosować do następujących typów danych:

  • NSString odwołania (tylko do odczytu)
  • NSArray odwołania (tylko do odczytu)
  • 32-bitowe liczbySystem.Int32 ()
  • 64-bitowe liczbySystem.Int64 ()
  • 32-bitowe zmiennoprzecinki (System.Single)
  • 64-bitowe zmiennoprzecinki (System.Double)
  • System.Drawing.SizeF
  • CGSize

Oprócz nazwy pola natywnego można określić nazwę biblioteki, w której znajduje się pole, przekazując nazwę biblioteki:

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

Jeśli łączysz się statycznie, nie ma biblioteki do powiązania, więc musisz użyć __Internal nazwy:

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

Wyliczenia powiązań

Możesz dodać enum bezpośrednio w plikach powiązań, aby ułatwić korzystanie z nich wewnątrz definicji interfejsu API — bez użycia innego pliku źródłowego (który należy skompilować zarówno w powiązaniach, jak i w końcowym projekcie).

Przykład:

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

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

Istnieje również możliwość utworzenia własnych wyliczenia w celu zastąpienia NSString stałych. W takim przypadku generator automatycznie utworzy metody konwersji wartości wyliczenia i stałych NSString.

Przykład:

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

W powyższym przykładzie możesz zdecydować się na udekorowanie void Perform (NSString mode); atrybutem [Internal] . Spowoduje to ukrycie interfejsu API opartego na stałej przed użytkownikami powiązań.

Jednak ograniczyłoby to podklasę typu, ponieważ alternatywna metoda interfejsu API nicer używa atrybutu [Wrap] . Te wygenerowane metody nie virtualsą , tj. nie będzie można ich zastąpić — co może być dobrym wyborem.

Alternatywą jest oznaczenie oryginalnej, NSStringopartej na definicji jako [Protected]. Umożliwi to działanie podklasy, jeśli jest to wymagane, a wersja zawijania będzie nadal działać i wywoła metodę zastąpienia.

Wiązanie NSValue, NSNumberi NSString z lepszym typem

Atrybut [BindAs] umożliwia powiązanie NSNumberi NSValueNSString(wyliczenia) do bardziej dokładnych typów języka C#. Atrybut może służyć do tworzenia lepszego, dokładniejszego interfejsu API platformy .NET za pośrednictwem natywnego interfejsu API.

Metody (przy wartości zwracanej) można dekorować za pomocą parametrów i właściwości za pomocą polecenia [BindAs]. Jedynym ograniczeniem jest to, że członek nie może znajdować się wewnątrz elementu [Protocol] lub [Model] interfejs.

Na przykład:

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

Czy dane wyjściowe:

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

Wewnętrznie wykonamy konwersje i CGRect.>NSValue<NSNumber>bool?<

[BindAs] obsługuje również tablice NSNumberNSValue i NSString(wyliczenia).

Na przykład:

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

Czy dane wyjściowe:

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

CAScrollNSString to wyliczenie oparte na kopii zapasowej. Pobierzemy odpowiednią NSString wartość i obsłużymy konwersję typu.

Zapoznaj się z dokumentacją, [BindAs] aby zapoznać się z obsługiwanymi typami konwersji.

Powiadomienia o powiązaniach

Powiadomienia to komunikaty, które są publikowane w obiekcie NSNotificationCenter.DefaultCenter i są używane jako mechanizm emisji komunikatów z jednej części aplikacji do innej. Deweloperzy subskrybują powiadomienia zwykle przy użyciu metody AddObserver NSNotificationCenter. Gdy aplikacja publikuje komunikat w centrum powiadomień, zazwyczaj zawiera ładunek przechowywany w słowniku NSNotification.UserInfo . Ten słownik jest słabo wpisany, a pobieranie z niego informacji jest podatne na błędy, ponieważ użytkownicy zazwyczaj muszą przeczytać w dokumentacji, które klucze są dostępne w słowniku i typy wartości, które mogą być przechowywane w słowniku. Obecność kluczy czasami jest używana jako wartość logiczna.

Generator powiązań platformy Xamarin.iOS zapewnia obsługę deweloperów w celu powiązania powiadomień. W tym celu należy ustawić [Notification] atrybut właściwości, która została również otagowana za pomocą elementu [Field] właściwość (może być publiczna lub prywatna).

Ten atrybut może być używany bez argumentów dla powiadomień, które nie mają ładunku, lub można określić, że odwołuje się do System.Type innego interfejsu w definicji interfejsu API, zazwyczaj z nazwą kończącą się ciągiem "EventArgs". Generator przekształci interfejs w klasę, która podklasy EventArgs i będzie zawierać wszystkie wymienione tam właściwości. Atrybut [Export] powinien być używany w klasie EventArgs, aby wyświetlić nazwę klucza używanego do wyszukania słownika Objective-C w celu pobrania wartości.

Na przykład:

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

Powyższy kod spowoduje wygenerowanie zagnieżdżonej klasy MyClass.Notifications przy użyciu następujących metod:

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

Użytkownicy kodu mogą następnie łatwo subskrybować powiadomienia publikowane w NSDefaultCenter przy użyciu kodu w następujący sposób:

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

Zwrócona wartość z ObserveDidStart może służyć do łatwego zatrzymywania odbierania powiadomień, w następujący sposób:

token.Dispose ();

Możesz też wywołać serwer NSNotification.DefaultCenter.RemoveObserver i przekazać token. Jeśli powiadomienie zawiera parametry, należy określić interfejs pomocnika EventArgs , w następujący sposób:

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

Powyższe spowoduje MyScreenChangedEventArgs wygenerowanie klasy z właściwościami ScreenX i ScreenY , które będą pobierać dane ze słownika NSNotification.UserInfo przy użyciu kluczy "ScreenXKey" i "ScreenYKey" odpowiednio i zastosuje odpowiednie konwersje. Atrybut [ProbePresence] jest używany do sondowania generatora, jeśli klucz jest ustawiony w elemecie UserInfo, zamiast próbować wyodrębnić wartość. Jest to używane w przypadkach, gdy obecność klucza jest wartością (zazwyczaj dla wartości logicznych).

Umożliwia to pisanie kodu w następujący sposób:

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

Kategorie powiązań

Kategorie to Objective-C mechanizm służący do rozszerzania zestawu metod i właściwości dostępnych w klasie. W praktyce są one używane do rozszerzania funkcjonalności klasy bazowej (na przykład NSObject), gdy określona struktura jest połączona (na przykład UIKit), udostępniając ich metody, ale tylko wtedy, gdy nowa struktura jest połączona. W niektórych innych przypadkach są one używane do organizowania funkcji w klasie według funkcji. Są one podobne do metod rozszerzeń języka C#. Oto, jak wygląda kategoria w pliku Objective-C:

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

Powyższy przykład, jeśli zostanie znaleziony w bibliotece, rozszerzy UIView wystąpienia klasy za pomocą metody makeBackgroundRed.

Aby je powiązać, możesz użyć atrybutu [Category] w definicji interfejsu. W przypadku korzystania z elementu [Category] atrybut, znaczenie elementu [BaseType] atrybut zmienia się z używanego do określania klasy bazowej do rozszerzenia, aby być typem, który ma zostać rozszerzony.

Poniżej pokazano, jak UIView rozszerzenia są powiązane i przekształcone w metody rozszerzeń języka C#:

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

Powyższe spowoduje MyUIViewExtension utworzenie klasy zawierającej metodę MakeBackgroundRed rozszerzenia. Oznacza to, że teraz można wywołać metodę "MakeBackgroundRed" w dowolnej UIView podklasie, co zapewnia taką samą funkcjonalność, jaką można uzyskać w systemie Objective-C. W niektórych innych przypadkach kategorie nie są używane do rozszerzania klasy systemowej, ale do organizowania funkcjonalności wyłącznie w celach dekoracyjnych. Jak to:

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

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

Chociaż można użyć [Category] atrybut również dla tego stylu dekoracji deklaracji, można również dodać je wszystkie do definicji klasy. Oba te elementy byłyby takie same:

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

W tych przypadkach scalanie kategorii jest po prostu krótsze:

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

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

Bloki powiązań

Bloki to nowa konstrukcja wprowadzona przez firmę Apple w celu wprowadzenia funkcjonalnego odpowiednika metod anonimowych języka C# do Objective-Cmetody . Na przykład NSSet klasa uwidacznia teraz tę metodę:

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

Powyższy opis deklaruje metodę o nazwie enumerateObjectsUsingBlock: , która przyjmuje jeden argument o nazwie block. Ten blok jest podobny do metody anonimowej języka C#, która obsługuje przechwytywanie bieżącego środowiska (wskaźnik "ten", dostęp do zmiennych lokalnych i parametrów). Powyższa metoda w NSSet metodzie wywołuje blok z dwoma parametrami (id objNSObject część) i wskaźnikiem do części logicznej (część BOOL *stop).

Aby powiązać ten rodzaj interfejsu API za pomocą funkcji btouch, należy najpierw zadeklarować podpis typu bloku jako delegat języka C#, a następnie odwołać się do niego z punktu wejścia interfejsu API w następujący sposób:

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

Teraz kod może wywołać funkcję z poziomu języka 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);
});

Jeśli wolisz, możesz również użyć wyrażeń 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);
});

Metody asynchroniczne

Generator powiązań może przekształcić określoną klasę metod w metody przyjazne dla asynchronicznego (metody zwracające zadanie lub zadanie<T>).

Możesz użyć polecenia [Async] atrybut metod, które zwracają void i których ostatni argument jest wywołaniem zwrotnym. Po zastosowaniu tej metody do metody generator powiązań wygeneruje wersję tej metody z sufiksem Async. Jeśli wywołanie zwrotne nie przyjmuje żadnych parametrów, zwracana wartość będzie wartością Task, jeśli wywołanie zwrotne przyjmuje parametr, wynik będzie wartością Task<T>. Jeśli wywołanie zwrotne przyjmuje wiele parametrów, należy ustawić ResultType wartość lub ResultTypeName , aby określić żądaną nazwę wygenerowanego typu, który będzie przechowywać wszystkie właściwości.

Przykład:

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

Powyższy kod wygeneruje zarówno metodę LoadFile, jak i:

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

Przeglądanie silnych typów dla słabych parametrów NSDictionary

W wielu miejscach interfejsu Objective-C API parametry są przekazywane jako słabo typizowane NSDictionary interfejsy API z określonymi kluczami i wartościami, ale są podatne na błędy (można przekazać nieprawidłowe klucze i nie uzyskać żadnych ostrzeżeń; można przekazać nieprawidłowe wartości i uzyskać żadnych ostrzeżeń) i frustrujące do użycia, ponieważ wymagają one wielu podróży do dokumentacji w celu wyszukania możliwych nazw kluczy i wartości.

Rozwiązaniem jest zapewnienie silnie typizowanej wersji, która udostępnia silnie typizowana wersja interfejsu API i za kulisami mapuje różne podstawowe klucze i wartości.

Jeśli na przykład Objective-C interfejs API zaakceptował NSDictionary element i został udokumentowany jako klucz XyzVolumeKey , który przyjmuje NSNumber wartość woluminu z wartością od 0,0 do 1.0, a XyzCaptionKey element przyjmuje ciąg, chcesz, aby użytkownicy mieli miły interfejs API, który wygląda następująco:

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

Właściwość Volume jest zdefiniowana jako zmienna zmiennoprzecinkowa dopuszczana do wartości null, ponieważ konwencja w Objective-C pliku nie wymaga, aby te słowniki miały wartość, dlatego istnieją scenariusze, w których wartość może nie być ustawiona.

W tym celu należy wykonać kilka czynności:

  • Utwórz silnie typizowana klasę, która tworzy podklasę DictionaryContainer i udostępnia różne metody pobierania i ustawiające dla każdej właściwości.
  • Zadeklaruj przeciążenia metod podejmowania NSDictionary nowej silnie typizowanej wersji.

Możesz utworzyć silnie typizowana klasę ręcznie lub użyć generatora, aby wykonać pracę. Najpierw dowiesz się, jak to zrobić ręcznie, aby zrozumieć, co się dzieje, a następnie podejście automatyczne.

W tym celu należy utworzyć plik pomocniczy, który nie przechodzi do interfejsu API kontraktu. To właśnie musisz napisać, aby utworzyć klasę 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
}

Następnie należy podać metodę otoki, która udostępnia interfejs API wysokiego poziomu na podstawie interfejsu API niskiego poziomu.

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

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

Jeśli interfejs API nie musi zostać zastąpiony, możesz bezpiecznie ukryć interfejs API oparty na NSDictionary przy użyciu polecenia Atrybut [Internal].

Jak widać, używamy elementu [Wrap] atrybut do uwidoczenia nowego punktu wejścia interfejsu API i udostępniamy go przy użyciu naszej silnie typizowanej XyzOptions klasy. Metoda otoki umożliwia również przekazanie wartości null.

Teraz jedną rzeczą, o której nie wspomnieliśmy, jest to, skąd XyzOptionsKeys pochodzą wartości. Zazwyczaj należy zgrupować klucze, które interfejs API ma powierzchnie w klasie statycznej, takiej jak , w XyzOptionsKeysnastępujący sposób:

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

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

Przyjrzyjmy się automatycznej obsłudze tworzenia tych silnie typiowanych słowników. Pozwala to uniknąć dużej ilości standardowych elementów i można zdefiniować słownik bezpośrednio w kontrakcie interfejsu API, zamiast używać pliku zewnętrznego.

Aby utworzyć słownik silnie typizowane, wprowadź interfejs w interfejsie API i udekoruj go za pomocą atrybutu StrongDictionary . Informuje to generator, że powinien utworzyć klasę o takiej samej nazwie jak interfejs, który będzie pochodzić z DictionaryContainer i zapewni silne metody dostępu typizowane dla niego.

Atrybut [StrongDictionary] przyjmuje jeden parametr, który jest nazwą klasy statycznej zawierającej klucze słownika. Następnie każda właściwość interfejsu stanie się silnie typizowanym akcesorem. Domyślnie kod będzie używać nazwy właściwości z sufiksem "Klucz" w klasie statycznej, aby utworzyć metodę dostępu.

Oznacza to, że tworzenie silnie typizowanej metody dostępu nie wymaga już pliku zewnętrznego ani konieczności ręcznego tworzenia metod pobierających i ustawiających dla każdej właściwości ani konieczności ręcznego wyszukiwania kluczy.

Oto, jak wyglądałoby całe powiązanie:

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

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

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

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

Jeśli musisz odwołać się do XyzOption elementu członkowskiego w innym polu (nie jest to nazwa właściwości z sufiksem Key), możesz ozdobić właściwość za pomocą [Export] atrybut o nazwie, której chcesz użyć.

Mapowania typów

W tej sekcji opisano sposób Objective-C mapowania typów na typy języka C#.

Typy proste

W poniższej tabeli przedstawiono sposób mapowania typów ze Objective-C świata i CocoaTouch na świat Xamarin.iOS:

Objective-C nazwa typu Xamarin.iOS Unified API type (Ujednolicony typ interfejsu API platformy Xamarin.iOS)
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString (więcej informacji na temat powiązania NSString) string
char * string (zobacz również: [PlainString])
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
Typy CoreFoundation (CF*) CoreFoundation.CF*
GLint nint
GLfloat nfloat
Typy podstaw (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

Tablice

Środowisko uruchomieniowe platformy Xamarin.iOS automatycznie zajmuje się konwertowaniem tablic języka C# na NSArrays i wykonywaniem konwersji z powrotem, więc na przykład wyimaginowana Objective-C metoda zwracająca NSArrayUIViewselement :

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

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

Jest powiązany w następujący sposób:

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

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

Chodzi o użycie silnie typizowanej tablicy języka C#, ponieważ umożliwi to środowisku IDE zapewnienie prawidłowego uzupełniania kodu z rzeczywistym typem bez wymuszania odgadnięcia przez użytkownika lub wyszukania dokumentacji w celu ustalenia rzeczywistego typu obiektów zawartych w tablicy.

W przypadkach, gdy nie można śledzić rzeczywistego najbardziej pochodnego typu zawartego w tablicy, można użyć NSObject [] jako wartości zwracanej.

Selektory

Selektory są wyświetlane w interfejsie Objective-C API jako typ SELspecjalny. Podczas tworzenia powiązania selektora należy zamapować typ na ObjCRuntime.Selector. Zazwyczaj selektory są uwidocznione w interfejsie API zarówno z obiektem, obiektem docelowym, jak i selektorem do wywołania w obiekcie docelowym. Podanie obu tych elementów w zasadzie odpowiada delegatowi języka C#: coś, co hermetyzuje zarówno metodę do wywołania, jak i obiekt do wywołania metody w.

Oto jak wygląda powiązanie:

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

W ten sposób metoda zwykle będzie używana w aplikacji:

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

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

Aby utworzyć kartę powiązania dla deweloperów języka C#, zazwyczaj udostępniasz metodę NSAction , która przyjmuje parametr, który umożliwia używanie delegatów języka C# i lambdów zamiast Target+Selector. W tym celu zwykle ukrywa się metodę SetTarget , flagując ją za pomocą elementu [Internal] atrybut, a następnie uwidocznić nową metodę pomocnika w następujący sposób:

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

Teraz kod użytkownika można napisać w następujący sposób:

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

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

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

Ciągi

Po powiązaniu metody, która przyjmuje metodę NSString, można zastąpić element typem ciągu języka C#, zarówno w przypadku zwracanych typów, jak i parametrów.

Jedynym przypadkiem, gdy możesz chcieć użyć bezpośrednio NSString , jest użycie ciągu jako tokenu. Aby uzyskać więcej informacji na temat ciągów i NSString, przeczytaj dokument Api Design on NSString (Projektowanie interfejsu API w usłudze NSString ).

W niektórych rzadkich przypadkach interfejs API może uwidaczniać ciąg podobny do języka C (char *) zamiast Objective-C ciągu (NSString *). W takich przypadkach można dodać adnotację do parametru za pomocą polecenia Atrybut [PlainString].

parametry out/ref

Niektóre interfejsy API zwracają wartości w parametrach lub przekazują parametry przy użyciu odwołania.

Zazwyczaj podpis wygląda następująco:

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

W pierwszym przykładzie pokazano typowy Objective-C idiom, który zwraca kody błędów, przekazywany jest wskaźnik do NSError wskaźnika, a po powrocie ustawiono wartość. Druga metoda pokazuje, jak Objective-C metoda może przyjąć obiekt i zmodyfikować jego zawartość. Byłoby to przekazywanie przez odwołanie, a nie czysta wartość wyjściowa.

Powiązanie wygląda następująco:

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

Atrybuty zarządzania pamięcią

Jeśli używasz atrybutu [Export] i przekazujesz dane, które będą przechowywane przez wywołaną metodę, możesz określić semantyka argumentów, przekazując je jako drugi parametr, na przykład:

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

Powyższe oznaczałoby wartość jako semantykę "Zachowaj". Dostępne semantyka to:

  • Przypisywanie
  • Kopiuj
  • Zachowaj

Wskazówki dotyczące stylu

Korzystanie z funkcji [Internal]

Możesz użyć polecenia [Internal] atrybut ukrywania metody z publicznego interfejsu API. Możesz to zrobić w przypadkach, gdy uwidoczniony interfejs API jest zbyt niski i chcesz udostępnić implementację wysokiego poziomu w osobnym pliku na podstawie tej metody.

Można to również użyć, gdy napotkasz ograniczenia w generatorze powiązań, na przykład niektóre zaawansowane scenariusze mogą uwidaczniać typy, które nie są powiązane, i chcesz powiązać je na swój własny sposób i chcesz opakowować te typy samodzielnie.

Programy obsługi zdarzeń i wywołania zwrotne

Objective-C klasy zazwyczaj emitować powiadomienia lub żądać informacji przez wysłanie komunikatu do klasy delegata (Objective-C delegat).

Ten model, chociaż jest w pełni obsługiwany i uwidożony przez system Xamarin.iOS, czasami może być uciążliwy. Platforma Xamarin.iOS uwidacznia wzorzec zdarzeń języka C# i system wywołania zwrotnego metody w klasie, która może być używana w tych sytuacjach. Dzięki temu kod może zostać uruchomiony w następujący sposób:

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

Generator powiązań może zmniejszyć ilość wpisywania wymaganego do mapowania Objective-C wzorca na wzorzec języka C#.

Począwszy od platformy Xamarin.iOS 1.4 możliwe będzie również poinstruowanie generatora o utworzeniu powiązań dla określonych Objective-C delegatów i uwidocznienie delegata jako zdarzeń i właściwości języka C# w typie hosta.

Istnieją dwie klasy związane z tym procesem, klasa hosta, która będzie jedną, która obecnie emituje zdarzenia i wysyła je do Delegate klasy lub WeakDelegate i rzeczywistej klasy delegata.

Biorąc pod uwagę następującą konfigurację:

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

Aby opakować klasę, musisz:

  • W klasie hosta dodaj element do elementu [BaseType]
    zadeklarować typ, który działa jako jego delegat i nazwę języka C#, która została udostępniona. W naszym przykładzie powyżej są one typeof (MyClassDelegate) i WeakDelegate odpowiednio.
  • W klasie delegata w każdej metodzie, która ma więcej niż dwa parametry, należy określić typ, którego chcesz użyć dla automatycznie wygenerowanej klasy EventArgs.

Generator powiązań nie jest ograniczony do zawijania tylko jednego miejsca docelowego zdarzenia, istnieje możliwość, że niektóre Objective-C klasy emitują komunikaty do więcej niż jednego delegata, więc trzeba będzie udostępnić tablice do obsługi tej konfiguracji. Większość konfiguracji nie będzie jej potrzebować, ale generator jest gotowy do obsługi tych przypadków.

Wynikowy kod będzie:

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

Parametr EventArgs służy do określania nazwy EventArgs klasy do wygenerowania. Należy użyć jednej na podpis (w tym przykładzie EventArgs właściwość będzie zawierać With właściwość typu nint).

W powyższych definicjach generator utworzy następujące zdarzenie w wygenerowanej klasie MyClass:

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

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

Teraz możesz użyć kodu w następujący sposób:

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

Wywołania zwrotne są podobne do wywołań zdarzeń, różnica polega na tym, że zamiast mieć wielu potencjalnych subskrybentów (na przykład wiele metod może podłączyć się do Clicked zdarzenia lub DownloadFinished zdarzenia) wywołania zwrotne mogą mieć tylko jednego subskrybenta.

Proces jest identyczny, jedyną różnicą jest to, że zamiast uwidaczniać nazwę EventArgs klasy, która zostanie wygenerowana, eventArgs rzeczywiście jest używana do nazywania wynikowej nazwy delegata języka C#.

Jeśli metoda w klasie delegata zwraca wartość, generator powiązań zamapuje go na metodę delegata w klasie nadrzędnej zamiast zdarzenia. W takich przypadkach należy podać wartość domyślną, która powinna zostać zwrócona przez metodę, jeśli użytkownik nie podłącza się do delegata. W tym celu należy użyć polecenia [DefaultValue] lub [DefaultValueFromArgument] atrybuty.

[DefaultValue] program zakoduje wartość zwracaną, podczas gdy [DefaultValueFromArgument] służy do określania, który argument wejściowy zostanie zwrócony.

Wyliczenia i typy podstawowe

Można również odwoływać się do wyliczenia lub typów podstawowych, które nie są bezpośrednio obsługiwane przez system definicji interfejsu btouch. W tym celu umieść wyliczenia i typy podstawowe w osobnym pliku i dołącz je jako część jednego z dodatkowych plików, które udostępniasz w celu btouch.

Łączenie zależności

Jeśli tworzysz powiązania interfejsów API, które nie są częścią aplikacji, musisz upewnić się, że plik wykonywalny jest połączony z tymi bibliotekami.

Należy poinformować platformę Xamarin.iOS, jak połączyć biblioteki. Można to zrobić, zmieniając konfigurację kompilacji, aby wywołać mtouch polecenie za pomocą dodatkowych argumentów kompilacji, które określają sposób łączenia z nowymi bibliotekami przy użyciu opcji "-gcc_flags", a następnie cytowany ciąg zawierający wszystkie dodatkowe biblioteki wymagane dla programu, Jak to:

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

Powyższy przykład spowoduje połączenie libMyLibrary.alibSystemLibrary.dylib biblioteki i biblioteki platformy z końcowym plikiem CFNetwork wykonywalnym.

Możesz też skorzystać z zestawu na poziomie [LinkWithAttribute]zestawu, który można osadzić w plikach kontraktu (na przykład AssemblyInfo.cs). Jeśli używasz [LinkWithAttribute]elementu , musisz mieć bibliotekę natywną dostępną w momencie tworzenia powiązania, ponieważ spowoduje to osadzenie biblioteki natywnej w aplikacji. Na przykład:

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

Być może zastanawiasz się, dlaczego potrzebujesz -force_load polecenia, a powodem jest to, że flaga -ObjC, chociaż kompiluje kod w, nie zachowuje metadanych wymaganych do obsługi kategorii (konsolidator/kompilator nieaktywny usuwa kod), które są potrzebne w czasie wykonywania dla platformy Xamarin.iOS.

Odwołania wspomagane

Niektóre przejściowe obiekty, takie jak arkusze akcji i pola alertów, są kłopotliwe, aby śledzić dla deweloperów, a generator powiązań może pomóc trochę tutaj.

Jeśli na przykład masz klasę, która wyświetlała komunikat, a następnie wygenerowała Done zdarzenie, tradycyjnym sposobem obsługi tego zdarzenia jest:

class Demo {
    MessageBox box;

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

W powyższym scenariuszu deweloper musi zachować odwołanie do samego obiektu i wyciek lub aktywnie wyczyścić odwołanie do pola samodzielnie. Podczas tworzenia kodu powiązania generator obsługuje śledzenie odwołania i czyszczenie go po wywołaniu specjalnej metody, powyższy kod stanie się wtedy:

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

Zwróć uwagę, że nie jest już konieczne zachowanie zmiennej w wystąpieniu, że działa ze zmienną lokalną i że nie jest konieczne wyczyszczenie odwołania, gdy obiekt umiera.

Aby skorzystać z tego, klasa powinna mieć właściwość Events ustawioną w [BaseType] deklaracji, a także KeepUntilRef zmienną ustawioną na nazwę metody wywoływanej po zakończeniu pracy obiektu w następujący sposób:

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

Dziedziczenie protokołów

Począwszy od platformy Xamarin.iOS w wersji 3.2, obsługujemy dziedziczenie po protokołach oznaczonych właściwością [Model] . Jest to przydatne w niektórych wzorcach interfejsu API, takich jak w przypadku, gdy MapKitMKOverlay protokół dziedziczy z MKAnnotation protokołu i jest używany przez wiele klas dziedziczy z NSObjectklasy .

Historycznie wymagaliśmy skopiowania protokołu do każdej implementacji, ale w tych przypadkach możemy teraz mieć klasę MKShape dziedziczącą po MKOverlay protokole i automatycznie wygeneruje wszystkie wymagane metody.