Projekt interfejsu API platformy Xamarin.iOS

Oprócz podstawowych bibliotek klas bazowych, które są częścią mono, platforma Xamarin.iOS jest dostarczana z powiązaniami dla różnych interfejsów API systemu iOS, aby umożliwić deweloperom tworzenie natywnych aplikacji systemu iOS za pomocą platformy Mono.

W rdzeniu platformy Xamarin.iOS istnieje aparat międzyoperacyjny, który łączy świat C# ze światem Objective-C i powiązania interfejsów API opartych na języku C systemu iOS, takich jak CoreGraphics i OpenGL ES.

Środowisko uruchomieniowe niskiego poziomu do komunikowania się z Objective-C kodem jest w trybie MonoTouch.ObjCRuntime. Ponadto dostępne są powiązania dla zestawu Foundation, CoreFoundation i UIKit .

Zasady projektowania

W tej sekcji opisano niektóre z naszych zasad projektowania powiązań platformy Xamarin.iOS (dotyczą one również platformy Xamarin.Mac, powiązań Mono dla Objective-C systemu macOS):

  • Postępuj zgodnie z wytycznymi dotyczącymi projektowania struktury

  • Zezwalaj deweloperom na klasy podklasy Objective-C :

    • Uzyskiwanie danych z istniejącej klasy
    • Wywoływanie konstruktora podstawowego do łańcucha
    • Metody zastępowania należy wykonać za pomocą systemu zastępowania języka C#.
    • Podklasy powinny współdziałać ze standardowymi konstrukcjami języka C#
  • Nie ujawniaj deweloperom selektorów Objective-C

  • Udostępnianie mechanizmu wywoływania dowolnych Objective-C bibliotek

  • Ułatwianie wykonywania typowych Objective-C zadań i zadań twardych Objective-C

  • Uwidaczniaj Objective-C właściwości jako właściwości języka C#

  • Uwidocznienie silnie typizowanego interfejsu API:

    • Zwiększanie bezpieczeństwa typów
    • Minimalizuj błędy środowiska uruchomieniowego
    • Uzyskiwanie funkcji IntelliSense środowiska IDE dla typów zwracanych
    • Zezwala na dokumentację podręczną środowiska IDE
  • Zachęcamy do eksplorowania interfejsów API w środowisku IDE:

    • Na przykład zamiast uwidaczniać słabo typizowane tablicy, w następujący sposób:

      NSArray *getViews
      

      Uwidacznia silny typ w następujący sposób:

      NSView [] Views { get; set; }
      

      Użycie silnych typów daje Visual Studio dla komputerów Mac możliwość autouzupełniania podczas przeglądania interfejsu API, udostępnia wszystkie System.Array operacje w zwracanej wartości i umożliwia zwracaną wartość do udziału w LINQ.

  • Natywne typy języka C#:

    • NSString Staje się string

    • Turn int i uint parametry, które powinny zostać wyliczone do wyliczenia języka C# i wyliczenia języka C# z atrybutami [Flags]

    • Zamiast obiektów neutralnych NSArray dla typu uwidacznia tablice jako silnie typizowane tablice.

    • W przypadku zdarzeń i powiadomień należy wybrać między użytkownikami:

      • Domyślnie silnie typizowana wersja
      • Wersja ze słabym typem dla zaawansowanych przypadków użycia
  • Obsługa wzorca delegata Objective-C :

    • System zdarzeń języka C#
    • Uwidacznianie delegatów języka C# (lambda, metody anonimowe i System.Delegate) do Objective-C interfejsów API jako bloków

Zestawy

Platforma Xamarin.iOS zawiera wiele zestawów, które stanowią profil platformy Xamarin.iOS. Strona Zestawy zawiera więcej informacji.

Główne przestrzenie nazw

ObjCRuntime

Przestrzeń nazw ObjCRuntime umożliwia deweloperom łączenie światów między językami C# i Objective-C. Jest to nowe powiązanie, przeznaczone specjalnie dla systemu iOS, oparte na środowisku cocoa# i Gtk#.

Podstawowy

Przestrzeń nazw Foundation udostępnia podstawowe typy danych przeznaczone do współdziałania ze strukturą Objective-C Foundation, która jest częścią systemu iOS i jest podstawą programowania obiektowego w programie Objective-C.

Xamarin.iOS dubluje w języku C# hierarchię klas z klasy .Objective-C Na przykład klasa bazowa Objective-C NSObject może być dostępna w języku C# za pośrednictwem obiektu Foundation.NSObject.

Mimo że przestrzeń nazw foundation udostępnia powiązania dla bazowych Objective-C typów foundation, w kilku przypadkach zamapowaliśmy typy bazowe na typy platformy .NET. Na przykład:

  • Zamiast radzić sobie z ciągami NSString i NSArray, środowisko uruchomieniowe uwidacznia je jako ciągijęzyka C# i silnie typizowane tablicew całym interfejsie API.

  • Różne interfejsy API pomocnika są tutaj udostępniane, aby umożliwić deweloperom powiązanie interfejsów API innych firm Objective-C , innych interfejsów API systemu iOS lub interfejsów API, które nie są obecnie powiązane przez platformę Xamarin.iOS.

Aby uzyskać więcej informacji na temat powiązań interfejsów API, zobacz sekcję Generator powiązań platformy Xamarin.iOS.

NSObject

Typ NSObject jest podstawą wszystkich Objective-C powiązań. Typy Xamarin.iOS dublować dwie klasy typów z interfejsów API cocoaTouch systemu iOS: typy C (zazwyczaj nazywane typami CoreFoundation) i Objective-C typy (które pochodzą z klasy NSObject).

Dla każdego typu dublowanego typu niezarządzanego można uzyskać obiekt macierzysty za pomocą właściwości Handle .

Podczas gdy mono zapewni odzyskiwanie pamięci dla wszystkich obiektów, Foundation.NSObject implementuje interfejs System.IDisposable . Możesz jawnie zwolnić zasoby dowolnego obiektu NSObject bez konieczności oczekiwania na rozpoczęcie modułu odśmiecającego odśmiecenie pamięci. Jawne zwalnianie zasobów jest ważne, gdy używasz dużych obiektów NSObject, na przykład UIImages, które mogą przechowywać wskaźniki do dużych bloków danych.

Jeśli typ musi wykonać deterministyczną finalizację, przesłoń metodę NSObject.Dispose(bool) Parametr dispose to "bool disposing" (usuwanie logiczne), a jeśli ustawiono wartość true, oznacza to, że metoda Dispose jest wywoływana, ponieważ użytkownik jawnie o nazwie Dispose () w obiekcie. Wartość false oznacza, że metoda Dispose(bool dising) jest wywoływana z finalizatora w wątku finalizatora.

Kategorie

Począwszy od platformy Xamarin.iOS 8.10 można utworzyć Objective-C kategorie na podstawie języka C#.

Odbywa się to przy użyciu atrybutu Category , określając typ, który ma być rozszerzany jako argument atrybutu. Poniższy przykład spowoduje rozszerzenie NSString na przykład.

[Category (typeof (NSString))]

Każda metoda kategorii używa normalnego mechanizmu eksportowania metod do Objective-C używania atrybutu Export :

[Export ("today")]
public static string Today ()
{
    return "Today";
}

Wszystkie metody rozszerzenia zarządzanego muszą być statyczne, ale możliwe jest utworzenie Objective-C metod wystąpienia przy użyciu standardowej składni dla metod rozszerzeń w języku C#:

[Export ("toUpper")]
public static string ToUpper (this NSString self)
{
    return self.ToString ().ToUpper ();
}

a pierwszym argumentem metody rozszerzenia będzie wystąpienie, na którym wywołano metodę.

Kompletny przykład:

[Category (typeof (NSString))]
public static class MyStringCategory
{
    [Export ("toUpper")]
    static string ToUpper (this NSString self)
    {
        return self.ToString ().ToUpper ();
    }
}

W tym przykładzie dodasz natywną metodę wystąpienia toUpper do klasy NSString, którą można wywołać z Objective-Cklasy .

[Category (typeof (UIViewController))]
public static class MyViewControllerCategory
{
    [Export ("shouldAutoRotate")]
    static bool GlobalRotate ()
    {
        return true;
    }
}

Jednym ze scenariuszy, w którym jest to przydatne, jest dodanie metody do całego zestawu klas w bazie kodu, na przykład spowoduje to, że wszystkie UIViewController wystąpienia będą raportować, że mogą obracać:

[Category (typeof (UINavigationController))]
class Rotation_IOS6 {
      [Export ("shouldAutorotate:")]
      static bool ShouldAutoRotate (this UINavigationController self)
      {
          return true;
      }
}
PreserveAttribute

PreserveAttribute jest atrybutem niestandardowym służącym do pisania funkcji mtouch — narzędzia wdrażania platformy Xamarin.iOS — w celu zachowania typu lub elementu członkowskiego typu w fazie przetwarzania aplikacji w celu zmniejszenia rozmiaru.

Każdy element członkowski, który nie jest statycznie połączony przez aplikację, podlega usunięciu. W związku z tym ten atrybut służy do oznaczania elementów członkowskich, do których nie odwołuje się statycznie, ale które są nadal potrzebne przez aplikację.

Na przykład w przypadku dynamicznego tworzenia wystąpień typów możesz zachować domyślny konstruktor typów. Jeśli używasz serializacji XML, możesz zachować właściwości typów.

Ten atrybut można zastosować na każdym elemencie typu lub na samym typie. Jeśli chcesz zachować cały typ, możesz użyć składni [Preserve (AllMembers = true)] w typie.

Zestaw interfejsu użytkownika

Przestrzeń nazw UIKit zawiera mapowanie "jeden do jednego" do wszystkich składników interfejsu użytkownika tworzących aplikację CocoaTouch w postaci klas języka C#. Interfejs API został zmodyfikowany w celu przestrzegania konwencji używanych w języku C#.

Delegaci języka C# są udostępniane na potrzeby typowych operacji. Aby uzyskać więcej informacji, zobacz sekcję delegatów .

OpenGLES

W przypadku biblioteki OpenGLES dystrybuujemy zmodyfikowaną wersję interfejsu API OpenTK , powiązanie obiektowe z biblioteką OpenGL, które zostało zmodyfikowane w celu używania typów danych i struktur CoreGraphics oraz uwidacznianie tylko funkcji dostępnych w systemie iOS.

Funkcje openGLES 1.1 są dostępne za pośrednictwem typu ES11.GL.

Funkcje openGLES 2.0 są dostępne za pośrednictwem typu ES20.GL.

Funkcje openGLES 3.0 są dostępne za pośrednictwem typu ES30.GL.

Projekt powiązania

Platforma Xamarin.iOS nie jest tylko powiązaniem z podstawową Objective-C platformą. Rozszerza system typów platformy .NET i wysyła system w celu lepszego mieszania języka C# i Objective-C.

Podobnie jak P/Invoke to przydatne narzędzie do wywoływania bibliotek natywnych w systemach Windows i Linux lub jako obsługa IJW może służyć do międzyoperacyjności modelu COM w systemie Windows, platforma Xamarin.iOS rozszerza środowisko uruchomieniowe w celu obsługi powiązania obiektów C# z Objective-C obiektami.

Dyskusja w kilku następnych sekcjach nie jest konieczna dla użytkowników tworzących aplikacje platformy Xamarin.iOS, ale pomoże deweloperom zrozumieć, jak wszystko jest wykonywane i pomoże im podczas tworzenia bardziej skomplikowanych aplikacji.

Typy

Tam, gdzie miało to sens, typy języka C# są uwidocznione zamiast typów podstaw niskiego poziomu we wszechświecie języka C#. Oznacza to, że interfejs API używa typu "string" w języku C# zamiast NSString i używa silnie typicznych tablic języka C# zamiast uwidaczniania NSArray.

Ogólnie rzecz biorąc, w projekcie Xamarin.iOS i Xamarin.Mac obiekt źródłowy NSArray nie jest uwidoczniony. Zamiast tego środowisko uruchomieniowe automatycznie konwertuje NSArrays na silnie typizowane tablice niektórych NSObject klas. Dlatego platforma Xamarin.iOS nie uwidacznia słabo typizowanej metody, takiej jak GetViews, aby zwrócić NSArray:

NSArray GetViews ();

Zamiast tego powiązanie uwidacznia silnie typizowana wartość zwracaną w następujący sposób:

UIView [] GetViews ();

Istnieje kilka metod uwidocznionych w programie w NSArrayprzypadku przypadków narożnych, w których można użyć NSArray bezpośrednio, ale ich użycie jest odradzane w powiązaniu interfejsu API.

Ponadto w klasycznym interfejsie API zamiast uwidaczniać CGPointCGRectinterfejs API , i CGSize z interfejsu API CoreGraphics zastąpiliśmy je System.Drawing implementacjami RectangleF, PointFiSizeF, ponieważ ułatwiłyby deweloperom zachowanie istniejącego kodu OpenGL, który używa biblioteki OpenTK. W przypadku korzystania z nowego 64-bitowego ujednoliconego interfejsu API należy użyć interfejsu API CoreGraphics.

Dziedziczenie

Projekt interfejsu API platformy Xamarin.iOS umożliwia deweloperom rozszerzanie typów natywnych Objective-C w taki sam sposób, jak rozszerzenie typu C#, przy użyciu słowa kluczowego "override" w klasie pochodnej i łączenie w łańcuch do podstawowej implementacji przy użyciu słowa kluczowego "base" języka C#.

Ten projekt umożliwia deweloperom unikanie pracy z selektorami Objective-C w ramach procesu programowania, ponieważ cały Objective-C system jest już opakowany wewnątrz bibliotek platformy Xamarin.iOS.

Typy i konstruktor interfejsów

Podczas tworzenia klas platformy .NET, które są wystąpieniami typów utworzonych przez narzędzie Interface Builder, należy podać konstruktor, który przyjmuje jeden IntPtr parametr. Jest to wymagane do powiązania wystąpienia obiektu zarządzanego z niezarządzanymi obiektami. Kod składa się z jednego wiersza, w następujący sposób:

public partial class void MyView : UIView {
   // This is the constructor that you need to add.
   public MyView (IntPtr handle) : base (handle) {}
}

Delegaci

Objective-C i C# mają różne znaczenia dla delegata słowa w każdym języku.

Objective-C Na świecie i w dokumentacji, która znajdziesz online na temat CocoaTouch, delegat jest zazwyczaj wystąpieniem klasy, która będzie reagować na zestaw metod. Jest to podobne do interfejsu języka C#, a różnica polega na tym, że metody nie są zawsze obowiązkowe.

Ci delegaci odgrywają ważną rolę w zestawie UIKit i innych interfejsach API CocoaTouch. Są one używane do wykonywania różnych zadań:

  • Aby przekazać powiadomienia do kodu (podobnie jak dostarczanie zdarzeń w języku C# lub Gtk+).
  • Aby zaimplementować modele dla kontrolek wizualizacji danych.
  • Aby sterować zachowaniem kontrolki.

Wzorzec programowania został zaprojektowany w celu zminimalizowania tworzenia klas pochodnych w celu zmiany zachowania kontrolki. To rozwiązanie jest podobne do tego, co inne zestawy narzędzi GUI zrobiły przez lata: sygnały Gtk, gniazda Qt, zdarzenia Winforms, zdarzenia WPF/Silverlight itd. Aby uniknąć posiadania setek interfejsów (po jednym dla każdej akcji) lub wymaganie od deweloperów implementacji zbyt wielu metod, których nie potrzebują, Objective-C obsługuje opcjonalne definicje metod. Różni się to od interfejsów języka C#, które wymagają zaimplementowania wszystkich metod.

W Objective-C klasach zobaczysz, że klasy korzystające z tego wzorca programowania uwidaczniają właściwość o nazwie delegate, która jest wymagana do zaimplementowania obowiązkowych części interfejsu i zera lub więcej części opcjonalnych.

W systemie Xamarin.iOS dostępne są trzy wzajemnie wykluczające się mechanizmy związane z tymi delegatami:

  1. Za pośrednictwem zdarzeń.
  2. Silnie typizowane za pośrednictwem Delegate właściwości
  3. Luźno wpisane za pośrednictwem WeakDelegate właściwości

Rozważmy na przykład klasę UIWebView. Spowoduje to wysłanie do wystąpienia UIWebViewDelegate przypisanego do właściwości delegata.

Za pośrednictwem zdarzeń

W przypadku wielu typów platforma Xamarin.iOS automatycznie utworzy odpowiedni delegat, który będzie przekazywać wywołania do zdarzeń UIWebViewDelegate języka C#. W przypadku ramki UIWebView:

Na przykład ten prosty program rejestruje czas rozpoczęcia i zakończenia podczas ładowania widoku internetowego:

DateTime startTime, endTime;
var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.LoadStarted += (o, e) => startTime = DateTime.Now;
web.LoadFinished += (o, e) => endTime = DateTime.Now;
Za pomocą właściwości

Zdarzenia są przydatne, gdy może istnieć więcej niż jeden subskrybent zdarzenia. Ponadto zdarzenia są ograniczone do przypadków, w których nie ma wartości zwracanej z kodu.

W przypadku przypadków, w których kod ma zwrócić wartość, zamiast tego wybraliśmy właściwości. Oznacza to, że w danym momencie w obiekcie można ustawić tylko jedną metodę.

Na przykład można użyć tego mechanizmu, aby odrzucić klawiaturę na ekranie programu obsługi dla elementu UITextField:

void SetupTextField (UITextField tf)
{
    tf.ShouldReturn = delegate (textfield) {
        textfield.ResignFirstResponder ();
        return true;
    }
}

Właściwość UITextField"w ShouldReturn tym przypadku przyjmuje jako argument delegata, który zwraca wartość logiczną i określa, czy pole Tekstowe powinno zrobić coś przy naciśnięciu przycisku Return. W naszej metodzie zwracamy wartość true do obiektu wywołującego, ale usuwamy również klawiaturę z ekranu (dzieje się tak, gdy pole tekstowe wywołuje ResignFirstRespondermetodę ).

Silnie typizowane za pośrednictwem właściwości delegata

Jeśli nie chcesz używać zdarzeń, możesz podać własną podklasę UIWebViewDelegate i przypisać ją do właściwości UIWebView.Delegate . Po przypisaniu elementu UIWebView.Delegate mechanizm wysyłania zdarzeń UIWebView nie będzie już działać, a metody UIWebViewDelegate będą wywoływane po wystąpieniu odpowiednich zdarzeń.

Na przykład ten prosty typ rejestruje czas ładowania widoku internetowego:

class Notifier : UIWebViewDelegate  {
    DateTime startTime, endTime;

    public override LoadStarted (UIWebView webview)
    {
        startTime = DateTime.Now;
    }

    public override LoadingFinished (UIWebView webView)
    {
        endTime= DateTime.Now;
    }
}

Powyższe informacje są używane w kodzie w następujący sposób:

var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.Delegate = new Notifier ();

Powyższe polecenie spowoduje utworzenie programu UIWebViewer i poinstruuje go o wysłaniu komunikatów do wystąpienia usługi Notifier— klasy utworzonej w celu odpowiadania na komunikaty.

Ten wzorzec służy również do kontrolowania zachowania niektórych kontrolek, na przykład w przypadku UIWebView właściwość UIWebView.ShouldStartLoad umożliwia wystąpieniu UIWebView kontrolowanie, czy UIWebView załaduje stronę, czy nie.

Wzorzec jest również używany do dostarczania danych na żądanie dla kilku kontrolek. Na przykład kontrolka UITableView jest zaawansowaną kontrolką renderowania tabel — a zarówno wygląd, jak i zawartość są sterowane przez wystąpienie elementu UITableViewDataSource

Luźno wpisane za pomocą właściwości WeakDelegate

Oprócz silnie typizowanej właściwości istnieje również słaby delegat typu, który umożliwia deweloperowi powiązanie różnych elementów w razie potrzeby. Wszędzie, gdzie silnie typizowana Delegate właściwość jest widoczna w powiązaniu platformy Xamarin.iOS, uwidoczniona jest również odpowiednia WeakDelegate właściwość.

W przypadku korzystania z WeakDelegateklasy odpowiadasz za prawidłowe dekorowanie klasy przy użyciu atrybutu Eksportuj w celu określenia selektora. Na przykład:

class Notifier : NSObject  {
    DateTime startTime, endTime;

    [Export ("webViewDidStartLoad:")]
    public void LoadStarted (UIWebView webview)
    {
        startTime = DateTime.Now;
    }

    [Export ("webViewDidFinishLoad:")]
    public void LoadingFinished (UIWebView webView)
    {
        endTime= DateTime.Now;
    }
}

[...]

var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.WeakDelegate = new Notifier ();

Po przypisaniu WeakDelegateDelegate właściwości właściwość nie będzie używana. Ponadto w przypadku zaimplementowania metody w dziedziczonej klasie bazowej, którą chcesz wyeksportować, musisz ustawić ją jako metodę publiczną.

Mapowanie wzorca delegata Objective-C na C#

Gdy zobaczysz Objective-C przykłady, które wyglądają następująco:

foo.delegate = [[SomethingDelegate] alloc] init]

Spowoduje to, że język utworzy i skonstruuje wystąpienie klasy "SomethingDelegate" i przypisze wartość do właściwości delegata w zmiennej foo. Ten mechanizm jest obsługiwany przez środowisko Xamarin.iOS i C# składnię:

foo.Delegate = new SomethingDelegate ();

W środowisku Xamarin.iOS udostępniliśmy silnie typizowane klasy, które są mapowane na Objective-C klasy delegatów. Aby ich używać, będziesz podklasować i zastąpić metody zdefiniowane przez implementację platformy Xamarin.iOS. Aby uzyskać więcej informacji na temat sposobu ich działania, zobacz sekcję "Modele" poniżej.

Mapowanie delegatów na C#

Zestaw interfejsu użytkownika ogólnie używa Objective-C delegatów w dwóch formularzach.

Pierwszy formularz zawiera interfejs modelu składnika. Na przykład jako mechanizm udostępniania danych na żądanie dla widoku, takiego jak magazyn danych dla widoku listy. W takich przypadkach należy zawsze utworzyć wystąpienie odpowiedniej klasy i przypisać zmienną.

W poniższym przykładzie udostępniamy UIPickerView implementację modelu, który używa ciągów:

public class SampleTitleModel : UIPickerViewTitleModel {

    public override string TitleForRow (UIPickerView picker, nint row, nint component)
    {
        return String.Format ("At {0} {1}", row, component);
    }
}

[...]

pickerView.Model = new MyPickerModel ();

Drugi formularz polega na podaniu powiadomienia o zdarzeniach. W takich przypadkach, mimo że nadal uwidaczniamy interfejs API w formularzu opisanym powyżej, udostępniamy również zdarzenia języka C#, które powinny być prostsze w przypadku szybkich operacji i zintegrowane z anonimowymi delegatami i wyrażeniami lambda w języku C#.

Możesz na przykład subskrybować UIAccelerometer zdarzenia:

UIAccelerometer.SharedAccelerometer.Acceleration += (sender, args) => {
   UIAcceleration acc = args.Acceleration;
   Console.WriteLine ("Time={0} at {1},{2},{3}", acc.Time, acc.X, acc.Y, acc.Z);
}

Dostępne są dwie opcje, w których mają sens, ale jako programista musisz wybrać jedną lub drugą. Jeśli utworzysz własne wystąpienie silnie typizowanego osoby odpowiadającej/delegata i przypiszesz je, zdarzenia języka C# nie będą działać. Jeśli używasz zdarzeń języka C#, metody w klasie odpowiadającej/delegata nigdy nie będą wywoływane.

Poprzedni przykład, którego użyto UIWebView , można napisać przy użyciu wyrażeń lambd języka C# 3.0 w następujący sposób:

var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.LoadStarted += () => { startTime = DateTime.Now; }
web.LoadFinished += () => { endTime = DateTime.Now; }

Reagowanie na zdarzenia

W Objective-C kodzie czasami programy obsługi zdarzeń dla wielu kontrolek i dostawców informacji dla wielu kontrolek będą hostowane w tej samej klasie. Jest to możliwe, ponieważ klasy reagują na komunikaty i tak długo, jak klasy reagują na komunikaty, można połączyć obiekty razem.

Jak opisano wcześniej, platforma Xamarin.iOS obsługuje zarówno model programowania opartego na zdarzeniach języka C#, jak i Objective-C wzorzec delegata, w którym można utworzyć nową klasę, która implementuje delegata i zastępuje żądane metody.

Istnieje również możliwość obsługi Objective-Cwzorca, w którym osoby odpowiadające dla wielu różnych operacji są hostowane w tym samym wystąpieniu klasy. W tym celu należy jednak użyć funkcji niskiego poziomu powiązania platformy Xamarin.iOS.

Jeśli na przykład klasa ma odpowiadać na UITextFieldDelegate.textFieldShouldClearkomunikat : i UIWebViewDelegate.webViewDidStartLoad: w tym samym wystąpieniu klasy, musisz użyć deklaracji atrybutu [Export]:

public class MyCallbacks : NSObject {
    [Export ("textFieldShouldClear:"]
    public bool should_we_clear (UITextField tf)
    {
        return true;
    }

    [Export ("webViewDidStartLoad:")]
    public void OnWebViewStart (UIWebView view)
    {
        Console.WriteLine ("Loading started");
    }
}

Nazwy języka C# dla metod nie są ważne; wszystkie, co ma znaczenie, to ciągi przekazywane do atrybutu [Export].

W przypadku korzystania z tego stylu programowania upewnij się, że parametry języka C# są zgodne z rzeczywistymi typami przekazywanymi przez aparat środowiska uruchomieniowego.

Modele

W obiektach magazynu UIKit lub w obiektach odpowiadających, które są implementowane przy użyciu klas pomocników, są one przywoływane w Objective-C kodzie jako delegaty i są implementowane jako protokoły.

Objective-C Protokoły są podobne do interfejsów, ale obsługują opcjonalne metody — to znaczy, że nie wszystkie metody muszą być implementowane, aby protokół działał.

Istnieją dwa sposoby implementowania modelu. Można zaimplementować ją ręcznie lub użyć istniejących silnie typiowanych definicji.

Mechanizm ręczny jest niezbędny podczas próby zaimplementowania klasy, która nie została powiązana przez platformę Xamarin.iOS. Łatwo jest to zrobić:

  • Flaguj klasę na potrzeby rejestracji w środowisku uruchomieniowym
  • Zastosuj atrybut [Export] z rzeczywistą nazwą selektora dla każdej metody, którą chcesz zastąpić
  • Utwórz wystąpienie klasy i przekaż ją.

Na przykład następujące implementowanie tylko jednej z opcjonalnych metod w definicji protokołu UIApplicationDelegate:

public class MyAppController : NSObject {
        [Export ("applicationDidFinishLaunching:")]
        public void FinishedLaunching (UIApplication app)
        {
                SetupWindow ();
        }
}

Nazwa selektora Objective-C ("applicationDidFinishLaunching:") jest zadeklarowana za pomocą atrybutu Eksportuj, a klasa jest zarejestrowana za pomocą atrybutu [Register] .

Platforma Xamarin.iOS udostępnia silnie typizowane deklaracje, gotowe do użycia, które nie wymagają ręcznego powiązania. Aby obsługiwać ten model programowania, środowisko uruchomieniowe platformy Xamarin.iOS obsługuje atrybut [Model] w deklaracji klasy. Informuje to środowisko uruchomieniowe, że nie powinno połączyć wszystkich metod w klasie, chyba że metody zostaną jawnie zaimplementowane.

Oznacza to, że w zestawie UIKit klasy reprezentujące protokół z opcjonalnymi metodami są zapisywane w następujący sposób:

[Model]
public class SomeViewModel : NSObject {
    [Export ("someMethod:")]
    public virtual int SomeMethod (TheView view) {
       throw new ModelNotImplementedException ();
    }
    ...
}

Jeśli chcesz zaimplementować model, który implementuje tylko niektóre metody, wystarczy zastąpić interesujące Cię metody i zignorować inne metody. Środowisko uruchomieniowe będzie podłączyć tylko zastąpione metody, a nie oryginalne metody do Objective-C świata.

Odpowiednik poprzedniej próbki ręcznej to:

public class AppController : UIApplicationDelegate {
    public override void FinishedLaunching (UIApplication uia)
    {
     ...
    }
}

Zalety polegają na tym, że nie trzeba zagłębiać się w Objective-C pliki nagłówka w celu znalezienia selektora, typów argumentów lub mapowania na język C#, a także uzyskania funkcji IntelliSense z Visual Studio dla komputerów Mac wraz z silnymi typami

Gniazda XIB i C#

Jest to ogólny opis sposobu integracji placówek z językiem C# i jest udostępniany dla zaawansowanych użytkowników platformy Xamarin.iOS. W przypadku korzystania z Visual Studio dla komputerów Mac mapowanie odbywa się automatycznie w tle przy użyciu wygenerowanego kodu w locie.

Podczas projektowania interfejsu użytkownika za pomocą narzędzia Interface Builder będziesz projektować tylko wygląd aplikacji i ustanawiać niektóre domyślne połączenia. Jeśli chcesz programowo pobrać informacje, zmienić zachowanie kontrolki w czasie wykonywania lub zmodyfikować kontrolkę w czasie wykonywania, konieczne jest powiązanie niektórych kontrolek z kodem zarządzanym.

Odbywa się to w kilku krokach:

  1. Dodaj deklarację wylotu do właściciela pliku.
  2. Połączenie kontrolkę doWłaściciel pliku.
  3. Zapisz interfejs użytkownika oraz połączenia z plikiem XIB/NIB.
  4. Załaduj plik NIB w czasie wykonywania.
  5. Uzyskaj dostęp do zmiennej wylotowej.

Kroki od (1) do (3) zostały omówione w dokumentacji firmy Apple dotyczącej tworzenia interfejsów za pomocą narzędzia Interface Builder.

W przypadku korzystania z platformy Xamarin.iOS aplikacja musi utworzyć klasę pochodzącą z interfejsu UIViewController. Jest on implementowany w następujący sposób:

public class MyViewController : UIViewController {
    public MyViewController (string nibName, NSBundle bundle) : base (nibName, bundle)
    {
        // You can have as many arguments as you want, but you need to call
        // the base constructor with the provided nibName and bundle.
    }
}

Następnie, aby załadować element ViewController z pliku NIB, wykonaj następujące czynności:

var controller = new MyViewController ("HelloWorld", NSBundle.MainBundle, this);

Spowoduje to załadowanie interfejsu użytkownika z nib. Teraz, aby uzyskać dostęp do placówek, należy poinformować środowisko uruchomieniowe, że chcemy uzyskać do nich dostęp. W tym celu podklasa UIViewController musi zadeklarować właściwości i dodać do nich adnotacje za pomocą atrybutu [Połączenie]. Jak to:

[Connect]
UITextField UserName {
    get {
        return (UITextField) GetNativeField ("UserName");
    }
    set {
        SetNativeField ("UserName", value);
    }
}

Implementacja właściwości jest taka, która faktycznie pobiera i przechowuje wartość rzeczywistego typu natywnego.

Nie musisz się tym martwić podczas korzystania z Visual Studio dla komputerów Mac i InterfaceBuilder. Visual Studio dla komputerów Mac automatycznie dubluje wszystkie zadeklarowane punkty z kodem w klasie częściowej skompilowanej w ramach projektu.

Selektory

Podstawową koncepcją Objective-C programowania jest selektory. Często natkniesz się na interfejsy API, które wymagają przekazania selektora lub oczekuje, że kod odpowie na selektor.

Tworzenie nowych selektorów w języku C# jest łatwe — wystarczy utworzyć nowe wystąpienie ObjCRuntime.Selector klasy i użyć wyniku w dowolnym miejscu w interfejsie API, który go wymaga. Na przykład:

var selector_add = new Selector ("add:plus:");

Aby metoda języka C# odpowiedziała na wywołanie selektora, musi dziedziczyć z NSObject typu, a metoda języka C# musi być ozdobiona nazwą selektora przy użyciu atrybutu [Export] . Na przykład:

public class MyMath : NSObject {
    [Export ("add:plus:")]
    int Add (int first, int second)
    {
         return first + second;
    }
}

Nazwy selektorów muszą być dokładnie zgodne, w tym wszystkie dwukropki pośrednie i końcowe (":"), jeśli są obecne.

Konstruktory NSObject

Większość klas w systemie Xamarin.iOS, które pochodzą z NSObject klasy, uwidacznia konstruktory specyficzne dla funkcjonalności obiektu, ale będą również uwidaczniać różne konstruktory, które nie są natychmiast oczywiste.

Konstruktory są używane w następujący sposób:

public Foo (IntPtr handle)

Ten konstruktor służy do tworzenia wystąpienia klasy, gdy środowisko uruchomieniowe musi zamapować klasę na klasę niezarządzaną. Dzieje się tak podczas ładowania pliku XIB/NIB. W tym momencie Objective-C środowisko uruchomieniowe utworzy obiekt w świecie niezarządzanym, a ten konstruktor zostanie wywołany w celu zainicjowania zarządzanej strony.

Zazwyczaj wystarczy wywołać konstruktor podstawowy z parametrem uchwytu, a w treści wykonaj inicjację, która jest niezbędna.

public Foo ()

Jest to domyślny konstruktor klasy, a w klasach dostarczanych przez platformę Xamarin.iOS inicjuje klasę Foundation.NSObject i wszystkie klasy między elementami i na końcu w łańcuchu do Objective-Cinit metody w klasie.

public Foo (NSObjectFlag x)

Ten konstruktor służy do inicjowania wystąpienia, ale uniemożliwia kodowi wywoływanie Objective-C metody "init" na końcu. Zazwyczaj jest to używane podczas rejestrowania do inicjowania (w przypadku użycia [Export] w konstruktorze) lub podczas inicjowania za pomocą innej średniej.

public Foo (NSCoder coder)

Ten konstruktor jest dostarczany w przypadkach, w których obiekt jest inicjowany z wystąpienia NSCoding.

Wyjątki

Projekt interfejsu API platformy Xamarin.iOS nie zgłasza Objective-C wyjątków jako wyjątków języka C#. Projekt wymusza, że w pierwszej kolejności nie są wysyłane żadne śmieci na Objective-C świat i że wszelkie wyjątki, które należy wyprodukować, są generowane przez samo powiązanie, zanim kiedykolwiek zostaną przekazane nieprawidłowe dane na Objective-C świat.

Notifications

W systemach iOS i OS X deweloperzy mogą subskrybować powiadomienia emitowane przez platformę podstawową. Odbywa się to przy użyciu NSNotificationCenter.DefaultCenter.AddObserver metody . Metoda AddObserver przyjmuje dwa parametry: jedno to powiadomienie, do którego chcesz zasubskrybować; druga to metoda, która ma zostać wywołana podczas zgłaszania powiadomienia.

W obu środowiskach Xamarin.iOS i Xamarin.Mac klucze dla różnych powiadomień są hostowane w klasie, która wyzwala powiadomienia. Na przykład powiadomienia zgłaszane przez UIMenuController obiekt są hostowane jako static NSString właściwości w UIMenuController klasach, które kończą się nazwą "Powiadomienie".

Zarządzanie pamięcią

Platforma Xamarin.iOS ma moduł odśmiecania pamięci, który zajmie się wydawaniem zasobów, gdy nie są już używane. Oprócz modułu odśmiecanie pamięci wszystkie obiekty pochodzące z NSObject implementowania interfejsu System.IDisposable .

NSObject i IDisposable

Uwidacznianie interfejsu IDisposable jest wygodnym sposobem wspierania deweloperów w wydawaniu obiektów, które mogą hermetyzować duże bloki pamięci (na przykład może UIImage wyglądać jak tylko niewinny wskaźnik, ale może wskazywać na obraz 2 megabajt) i inne ważne i skończone zasoby (takie jak bufor dekodowania wideo).

Obiekt NSObject implementuje interfejs IDisposable, a także wzorzec .NET Dispose. Dzięki temu deweloperzy, którzy podklasy NSObject zastępują zachowanie Dispose i zwalniają własne zasoby na żądanie. Rozważmy na przykład ten kontroler widoku, który utrzymuje wokół wielu obrazów:

class MenuViewController : UIViewController {
    UIImage breakfast, lunch, dinner;
    [...]
    public override void Dispose (bool disposing)
    {
        if (disposing){
             if (breakfast != null) breakfast.Dispose (); breakfast = null;
             if (lunch != null) lunch.Dispose (); lunch = null;
             if (dinner != null) dinner.Dispose (); dinner = null;
        }
        base.Dispose (disposing)
    }
}

Gdy obiekt zarządzany jest usuwany, nie jest już przydatny. Nadal może istnieć odwołanie do obiektów, ale obiekt jest w tym momencie nieprawidłowy dla wszystkich intencji i celów. Niektóre interfejsy API platformy .NET zapewniają to, zgłaszając wyjątek ObjectDisposedException, jeśli spróbujesz uzyskać dostęp do dowolnych metod w usuniętym obiekcie, na przykład:

var image = UIImage.FromFile ("demo.png");
image.Dispose ();
image.XXX = false;  // this at this point is an invalid operation

Nawet jeśli nadal możesz uzyskać dostęp do zmiennej "image", jest to naprawdę nieprawidłowe odwołanie i nie wskazuje Objective-C już obiektu, który trzymał obraz.

Jednak usunięcie obiektu w języku C# nie oznacza, że obiekt musi zostać zniszczony. Wystarczy zwolnić odwołanie do obiektu w języku C#. Istnieje możliwość, że środowisko Cocoa mogło przechowywać odwołanie do własnego użytku. Jeśli na przykład ustawisz właściwość Image elementu UIImageView na obraz, a następnie usuniesz obraz, źródłowy element UIImageView zrobił własne odwołanie i zachowa odwołanie do tego obiektu do momentu jego zakończenia.

Kiedy wywołać metodę Dispose

Wywołaj metodę Dispose, gdy potrzebujesz platformy Mono, aby pozbyć się obiektu. Możliwy przypadek użycia polega na tym, że obiekt Mono nie ma wiedzy, że obiekt NSObject rzeczywiście przechowuje odwołanie do ważnego zasobu, takiego jak pamięć lub pula informacji. W takich przypadkach należy wywołać metodę Dispose, aby natychmiast zwolnić odwołanie do pamięci, zamiast czekać na mono do wykonania cyklu odzyskiwania pamięci.

Wewnętrznie, gdy mono tworzy odwołania NSString z ciągów języka C#, usunie je natychmiast, aby zmniejszyć ilość pracy, którą musi wykonać moduł odśmiecania pamięci. Mniejsza liczba obiektów, z którymi można sobie poradzić, tym szybciej będzie działać GC.

Kiedy zachować odwołania do obiektów

Jednym z efektów ubocznych, które ma automatyczne zarządzanie pamięcią, jest to, że GC pozby się nieużywanych obiektów, o ile nie ma do nich odwołań. Co czasami może mieć zaskakujące skutki uboczne, na przykład, jeśli utworzysz zmienną lokalną do przechowywania kontrolera widoku najwyższego poziomu lub okna najwyższego poziomu, a następnie te zniknęły za plecami.

Jeśli nie zachowasz odwołania w zmiennych statycznych lub wystąpień do obiektów, mono będzie na nich szczęśliwie wywoływać metodę Dispose() i zwolni odwołanie do obiektu. Ponieważ może to być jedyne wybitne odwołanie, Objective-C środowisko uruchomieniowe zniszczy obiekt.