Progettazione di API Xamarin.iOS

Oltre alle principali librerie di classi base che fanno parte di Mono, Xamarin.iOS viene fornito con associazioni per varie API iOS per consentire agli sviluppatori di creare applicazioni iOS native con Mono.

Al centro di Xamarin.iOS, è disponibile un motore di interoperabilità che collega il mondo C# al Objective-C mondo e i binding per le API basate su C iOS come CoreGraphics e OpenGL ES.

Il runtime di basso livello per comunicare con Objective-C il codice si trova in MonoTouch.ObjCRuntime. Vengono inoltre forniti binding per Foundation, CoreFoundation e UIKit .

Principi di progettazione

Questa sezione descrive in dettaglio alcuni dei principi di progettazione per le associazioni Xamarin.iOS (si applicano anche a Xamarin.Mac, le associazioni Mono per Objective-C in macOS):

  • Seguire le linee guida per la progettazione del framework

  • Consenti agli sviluppatori di sottoclassi Objective-C classi:

    • Derivare da una classe esistente
    • Chiamare il costruttore di base per concatenare
    • È consigliabile eseguire l'override dei metodi con il sistema di override di C#.
    • La sottoclasse deve funzionare con costrutti standard C#
  • Non esporre gli sviluppatori ai Objective-C selettori

  • Fornire un meccanismo per chiamare librerie arbitrarie Objective-C

  • Rendere le attività comuni Objective-C semplici e difficili Objective-C possibili

  • Esporre Objective-C le proprietà come proprietà C#

  • Esporre un'API fortemente tipizzata:

    • Aumentare la sicurezza dei tipi
    • Ridurre al minimo gli errori di runtime
    • Ottenere IntelliSense dell'IDE sui tipi restituiti
    • Consente la documentazione popup dell'IDE
  • Incoraggiare l'esplorazione in-IDE delle API:

    • Ad esempio, invece di esporre una matrice tipizzata in modo debole, come illustrato di seguito:

      NSArray *getViews
      

      Esporre un tipo sicuro, come illustrato di seguito:

      NSView [] Views { get; set; }
      

      L'uso di tipi sicuri offre Visual Studio per Mac la possibilità di eseguire il completamento automatico durante l'esplorazione dell'API, rende disponibili tutte le System.Array operazioni sul valore restituito e consente al valore restituito di partecipare a LINQ.

  • Tipi C# nativi:

    • NSString diventa string

    • Trasformare int e uint parametri che devono essere stati enumerazioni in enumerazioni C# ed enumerazioni C# con [Flags] attributi

    • Anziché oggetti indipendenti NSArray dal tipo, esporre matrici come matrici fortemente tipate.

    • Per gli eventi e le notifiche, offrire agli utenti una scelta tra:

      • Una versione fortemente tipizzata per impostazione predefinita
      • Una versione tipizzata in modo debole per i casi d'uso avanzati
  • Supportare il Objective-C modello delegato:

    • Sistema eventi C#
    • Esporre delegati C# (lambda, metodi anonimi e System.Delegate) alle Objective-C API come blocchi

Assembly

Xamarin.iOS include molti assembly che costituiscono il profilo Xamarin.iOS. La pagina Assembly contiene altre informazioni.

Spazi dei nomi principali

ObjCRuntime

Lo spazio dei nomi ObjCRuntime consente agli sviluppatori di collegare i mondi tra C# e Objective-C. Si tratta di una nuova associazione, progettata appositamente per iOS, basata sull'esperienza di Cocoa# e Gtk#.

Fondazione

Lo spazio dei nomi Foundation fornisce i tipi di dati di base progettati per interagire con il Objective-C framework Foundation che fa parte di iOS ed è la base per la programmazione orientata agli oggetti in Objective-C.

Xamarin.iOS esegue il mirror in C# la gerarchia delle classi da Objective-C. Ad esempio, la Objective-C classe base NSObject è utilizzabile da C# tramite Foundation.NSObject.

Sebbene lo spazio dei nomi Foundation fornisca associazioni per i tipi Di base sottostanti Objective-C , in alcuni casi è stato eseguito il mapping dei tipi sottostanti ai tipi .NET. Ad esempio:

  • Anziché gestire NSString e NSArray, il runtime li espone come stringheC# e matricifortemente tipate in tutta l'API.

  • Diverse API helper sono esposte qui per consentire agli sviluppatori di associare API di terze parti Objective-C , altre API iOS o API attualmente non associate a Xamarin.iOS.

Per altre informazioni sulle API di associazione, vedere la sezione Generatore di associazioni Xamarin.iOS.

NSObject

Il tipo NSObject è la base per tutte le Objective-C associazioni. I tipi Xamarin.iOS eseguano il mirroring di due classi di tipi dalle API CocoaTouch iOS: i tipi C (in genere denominati tipi CoreFoundation) e i Objective-C tipi (che derivano tutti dalla classe NSObject).

Per ogni tipo che rispecchia un tipo non gestito, è possibile ottenere l'oggetto nativo tramite la proprietà Handle .

Mentre Mono fornirà Garbage Collection per tutti gli oggetti, Foundation.NSObject implementa l'interfaccia System.IDisposable . È possibile rilasciare in modo esplicito le risorse di un determinato NSObject senza dover attendere l'avvio del Garbage Collector. Il rilascio esplicito delle risorse è importante quando si usano oggetti NSObject pesanti, ad esempio UIImage che potrebbero contenere puntatori a blocchi di dati di grandi dimensioni.

Se il tipo deve eseguire la finalizzazione deterministica, eseguire l'override del metodo NSObject.Dispose(bool) Il parametro per Dispose è "bool disposing" e se impostato su true significa che il metodo Dispose viene chiamato perché l'utente ha chiamato in modo esplicito Dispose () sull'oggetto. Un valore false indica che il metodo Dispose(bool disposing) viene chiamato dal finalizzatore nel thread del finalizzatore.

Categorie

A partire da Xamarin.iOS 8.10 è possibile creare Objective-C categorie da C#.

Questa operazione viene eseguita usando l'attributo Category , specificando il tipo da estendere come argomento all'attributo . L'esempio seguente estenderà NSString.

[Category (typeof (NSString))]

Ogni metodo di categoria usa il meccanismo normale per l'esportazione di metodi per Objective-C l'uso dell'attributo Export :

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

Tutti i metodi di estensione gestiti devono essere statici, ma è possibile creare Objective-C metodi di istanza usando la sintassi standard per i metodi di estensione in C#:

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

e il primo argomento del metodo di estensione sarà l'istanza in cui è stato richiamato il metodo.

Esempio completo:

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

In questo esempio verrà aggiunto un metodo di istanza toUpper nativo alla classe NSString, che può essere richiamata da Objective-C.

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

Uno scenario in cui ciò è utile consiste nell'aggiungere un metodo a un intero set di classi nella codebase, ad esempio, in modo da rendere tutte le UIViewController istanze report che possono ruotare:

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

PreserveAttribute è un attributo personalizzato usato per indicare mtouch, lo strumento di distribuzione Xamarin.iOS, per mantenere un tipo o un membro di un tipo, durante la fase in cui l'applicazione viene elaborata per ridurne le dimensioni.

Ogni membro che non è collegato in modo statico dall'applicazione è soggetto alla rimozione. Di conseguenza, questo attributo viene usato per contrassegnare i membri a cui non viene fatto riferimento in modo statico, ma che sono ancora necessari per l'applicazione.

Ad esempio, se si creano istanze dei tipi in modo dinamico, potrebbe essere necessario mantenere il costruttore predefinito dei tipi. Se si usa la serializzazione XML, potrebbe essere necessario mantenere le proprietà dei tipi.

È possibile applicare questo attributo in ogni membro di un tipo oppure nel tipo stesso. Se si desidera mantenere l'intero tipo, è possibile usare la sintassi [Preserve (AllMembers = true)] nel tipo .

UIKit

Lo spazio dei nomi UIKit contiene un mapping uno-a-uno a tutti i componenti dell'interfaccia utente che costituiscono CocoaTouch sotto forma di classi C#. L'API è stata modificata per seguire le convenzioni usate nel linguaggio C#.

I delegati C# vengono forniti per le operazioni comuni. Per altre informazioni, vedere la sezione delegati .

OpenGLES

Per OpenGLES, viene distribuita una versione modificata dell'API OpenTK , un'associazione orientata agli oggetti a OpenGL modificata per l'uso di strutture e tipi di dati CoreGraphics ed esponendo solo le funzionalità disponibili in iOS.

La funzionalità OpenGLES 1.1 è disponibile tramite il tipo di ES11.GL.

La funzionalità OpenGLES 2.0 è disponibile tramite il tipo di ES20.GL.

La funzionalità OpenGLES 3.0 è disponibile tramite il tipo di ES30.GL.

Progettazione binding

Xamarin.iOS non è semplicemente un'associazione alla piattaforma sottostante Objective-C . Estende il sistema di tipi .NET e invia il sistema per combinare meglio C# e Objective-C.

Proprio come P/Invoke è uno strumento utile per richiamare librerie native in Windows e Linux oppure, poiché il supporto di IJW può essere usato per l'interoperabilità COM in Windows, Xamarin.iOS estende il runtime per supportare l'associazione di oggetti C# agli Objective-C oggetti.

La discussione nelle prossime sezioni non è necessaria per gli utenti che creano applicazioni Xamarin.iOS, ma aiuteranno gli sviluppatori a capire come vengono eseguite le operazioni e li assisteranno durante la creazione di applicazioni più complesse.

Tipi

Dove ha senso, i tipi C# vengono esposti invece di tipi Di base di basso livello, all'universo C#. Ciò significa che l'API usa il tipo "string" C# anziché NSString e usa matrici C# fortemente tipate anziché esporre NSArray.

In generale, nella progettazione di Xamarin.iOS e Xamarin.Mac l'oggetto sottostante NSArray non viene esposto. Al contrario, il runtime converte NSArrayautomaticamente s in matrici fortemente tipate di alcune NSObject classi. Pertanto, Xamarin.iOS non espone un metodo tipizzato debole come GetViews per restituire un NSArray:

NSArray GetViews ();

L'associazione espone invece un valore restituito fortemente tipizzato, come illustrato di seguito:

UIView [] GetViews ();

Esistono alcuni metodi esposti in NSArray, per i casi d'angolo in cui si potrebbe voler usare direttamente un NSArray oggetto , ma il loro uso è sconsigliato nell'associazione API.

Inoltre, nell'API classica invece di esporre CGRect, CGPointe CGSize dall'API CoreGraphics, le sono state sostituite con le System.Drawing implementazioni RectangleF, PointFe SizeF perché consentono agli sviluppatori di mantenere il codice OpenGL esistente che usa OpenTK. Quando si usa la nuova API unificata a 64 bit, è necessario usare l'API CoreGraphics.

Ereditarietà

La progettazione dell'API Xamarin.iOS consente agli sviluppatori di estendere i tipi nativi Objective-C nello stesso modo in cui estenderebbero un tipo C#, usando la parola chiave "override" in una classe derivata e concatenando fino all'implementazione di base usando la parola chiave C# "base".

Questa progettazione consente agli sviluppatori di evitare di gestire Objective-C i selettori come parte del processo di sviluppo, perché l'intero Objective-C sistema è già sottoposto a wrapping all'interno delle librerie Xamarin.iOS.

Tipi e Generatore di interfacce

Quando si creano classi .NET che sono istanze di tipi creati da Interface Builder, è necessario fornire un costruttore che accetta un singolo IntPtr parametro. Questa operazione è necessaria per associare l'istanza dell'oggetto gestito all'oggetto non gestito. Il codice è costituito da una singola riga, come illustrato di seguito:

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

Delegati

Objective-C e C# hanno significati diversi per il delegato di parola in ogni linguaggio.

Objective-C Nel mondo, e nella documentazione che troverai online su CocoaTouch, un delegato è in genere un'istanza di una classe che risponderà a un set di metodi. Questo è simile a un'interfaccia C#, con la differenza che i metodi non sono sempre obbligatori.

Questi delegati svolgono un ruolo importante in UIKit e in altre API CocoaTouch. Vengono usati per eseguire varie attività:

  • Per fornire notifiche al codice (simile al recapito di eventi in C# o Gtk+).
  • Per implementare modelli per i controlli di visualizzazione dei dati.
  • Per guidare il comportamento di un controllo.

Il modello di programmazione è stato progettato per ridurre al minimo la creazione di classi derivate per modificare il comportamento di un controllo. Questa soluzione è simile agli altri toolkit GUI eseguiti negli anni: segnali di Gtk, slot Qt, eventi Winforms, eventi WPF/Silverlight e così via. Per evitare di avere centinaia di interfacce (una per ogni azione) o richiedere agli sviluppatori di implementare troppi metodi non necessari, Objective-C supporta le definizioni dei metodi facoltativi. Questa operazione è diversa dalle interfacce C# che richiedono l'implementazione di tutti i metodi.

Nelle Objective-C classi si noterà che le classi che usano questo modello di programmazione espongono una proprietà, denominata delegate, che è necessaria per implementare le parti obbligatorie dell'interfaccia e zero o più delle parti facoltative.

In Xamarin.iOS vengono offerti tre meccanismi che si escludono a vicenda per l'associazione a questi delegati:

  1. Tramite eventi.
  2. Fortemente tipizzato tramite una Delegate proprietà
  3. Tipizzato in modo libero tramite una WeakDelegate proprietà

Si consideri ad esempio la classe UIWebView. Viene inviato a un'istanza di UIWebViewDelegate, assegnata alla proprietà del delegato.

Tramite eventi

Per molti tipi, Xamarin.iOS creerà automaticamente un delegato appropriato, che inoltra le UIWebViewDelegate chiamate agli eventi C#. Per UIWebView:

Ad esempio, questo semplice programma registra le ore di inizio e di fine durante il caricamento di una visualizzazione Web:

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;
Tramite proprietà

Gli eventi sono utili quando potrebbero essere presenti più sottoscrittori per l'evento. Inoltre, gli eventi sono limitati ai casi in cui non è presente alcun valore restituito dal codice.

Per i casi in cui si prevede che il codice restituisca un valore, è stato scelto esplicitamente per le proprietà. Ciò significa che è possibile impostare un solo metodo in un determinato momento in un oggetto .

Ad esempio, è possibile usare questo meccanismo per chiudere la tastiera sullo schermo del gestore per un oggetto UITextField:

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

In questo caso la UITextFieldproprietà dell'oggetto ShouldReturn accetta come argomento un delegato che restituisce un valore bool e determina se l'oggetto TextField deve eseguire un'operazione con il pulsante Return premuto. Nel metodo viene restituito true al chiamante, ma si rimuove anche la tastiera dallo schermo (questo accade quando il campo di testo chiama ResignFirstResponder).

Fortemente tipizzato tramite una proprietà Delegate

Se si preferisce non usare gli eventi, è possibile specificare la propria sottoclasse UIWebViewDelegate e assegnarla alla proprietà UIWebView.Delegate . Una volta assegnato UIWebView.Delegate, il meccanismo di invio dell'evento UIWebView non funzionerà più e i metodi UIWebViewDelegate verranno richiamati quando si verificano gli eventi corrispondenti.

Ad esempio, questo tipo semplice registra il tempo necessario per caricare una visualizzazione Web:

class Notifier : UIWebViewDelegate  {
    DateTime startTime, endTime;

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

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

Il codice precedente viene usato nel codice simile al seguente:

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

Il codice precedente creerà un UIWebViewer e lo indicherà di inviare messaggi a un'istanza di Notifier, una classe creata per rispondere ai messaggi.

Questo modello viene usato anche per controllare il comportamento per determinati controlli, ad esempio nel caso UIWebView, la proprietà UIWebView.ShouldStartLoad consente all'istanza UIWebView di controllare se UIWebView caricherà o meno una pagina.

Il modello viene usato anche per fornire i dati su richiesta per alcuni controlli. Ad esempio, il controllo UITableView è un potente controllo di rendering delle tabelle e sia l'aspetto che il contenuto sono guidati da un'istanza di uiTableViewDataSource

Tipizzato in modo libero tramite la proprietà WeakDelegate

Oltre alla proprietà fortemente tipizzata, esiste anche un delegato tipizzato debole che consente allo sviluppatore di associare le cose in modo diverso, se necessario. Ovunque una proprietà fortemente tipizzata Delegate viene esposta nell'associazione di Xamarin.iOS, viene esposta anche una proprietà corrispondente WeakDelegate .

Quando si usa , l'utente WeakDelegateè responsabile della corretta decorazione della classe usando l'attributo Export per specificare il selettore. Ad esempio:

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

Una volta assegnata la WeakDelegate proprietà, la Delegate proprietà non verrà utilizzata. Inoltre, se si implementa il metodo in una classe di base ereditata da [Export], è necessario impostarlo come metodo pubblico.

Mapping del Objective-C modello delegato a C#

Quando vengono visualizzati Objective-C esempi simili al seguente:

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

In questo modo viene indicato al linguaggio di creare e costruire un'istanza della classe "SomethingDelegate" e assegnare il valore alla proprietà delegate nella variabile foo. Questo meccanismo è supportato da Xamarin.iOS e C# la sintassi è:

foo.Delegate = new SomethingDelegate ();

In Xamarin.iOS sono state fornite classi fortemente tipate che eseguono il Objective-C mapping alle classi delegate. Per usarli, si eseguirà la sottoclasse e si eseguirà l'override dei metodi definiti dall'implementazione di Xamarin.iOS. Per altre informazioni sul loro funzionamento, vedere la sezione "Modelli" di seguito.

Mapping di delegati a C#

UIKit in generale usa Objective-C delegati in due moduli.

Il primo modulo fornisce un'interfaccia al modello di un componente. Ad esempio, come meccanismo per fornire dati su richiesta per una visualizzazione, ad esempio la funzionalità di archiviazione dei dati per una visualizzazione Elenco. In questi casi, è sempre necessario creare un'istanza della classe appropriata e assegnare la variabile.

Nell'esempio seguente viene fornita un'implementazione UIPickerView per un modello che usa stringhe:

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

Il secondo modulo consiste nel fornire notifiche per gli eventi. In questi casi, anche se si espone ancora l'API nel formato descritto in precedenza, vengono forniti anche eventi C#, che devono essere più semplici da usare per operazioni rapide e integrati con delegati anonimi ed espressioni lambda in C#.

Ad esempio, è possibile sottoscrivere UIAccelerometer gli eventi:

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

Le due opzioni sono disponibili dove hanno senso, ma come programmatore è necessario scegliere uno o l'altro. Se si crea una propria istanza di un risponditore/delegato fortemente tipizzato e la si assegna, gli eventi C# non saranno funzionali. Se si usano gli eventi C#, i metodi nella classe risponditore/delegato non verranno mai chiamati.

L'esempio precedente usato UIWebView può essere scritto usando espressioni lambda C# 3.0 simili alle seguenti:

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

Risposta agli eventi

Nel Objective-C codice, a volte i gestori eventi per più controlli e provider di informazioni per più controlli verranno ospitati nella stessa classe. Ciò è possibile perché le classi rispondono ai messaggi e, purché le classi rispondano ai messaggi, è possibile collegare gli oggetti tra loro.

Come descritto in precedenza, Xamarin.iOS supporta sia il modello di programmazione basato su eventi C# che il Objective-C modello delegato, in cui è possibile creare una nuova classe che implementa il delegato ed esegue l'override dei metodi desiderati.

È anche possibile supportare Objective-Cil modello in cui i risponditori per più operazioni diverse sono tutti ospitati nella stessa istanza di una classe. A tale scopo, è necessario usare le funzionalità di basso livello dell'associazione Xamarin.iOS.

Ad esempio, se si vuole che la classe risponda sia al messaggio : che UIWebViewDelegate.webViewDidStartLoada UITextFieldDelegate.textFieldShouldClear: nella stessa istanza di una classe, è necessario usare la dichiarazione di attributo [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");
    }
}

I nomi C# per i metodi non sono importanti; tutte le cose importanti sono le stringhe passate all'attributo [Export].

Quando si usa questo stile di programmazione, assicurarsi che i parametri C# corrispondano ai tipi effettivi passati dal motore di runtime.

Modelli

Nelle strutture di archiviazione UIKit o nei risponditori implementati usando le classi helper, si fa riferimento al Objective-C codice come delegati e vengono implementati come protocolli.

Objective-C i protocolli sono come interfacce, ma supportano metodi facoltativi, ovvero non tutti i metodi devono essere implementati per il funzionamento del protocollo.

Esistono due modi per implementare un modello. È possibile implementarlo manualmente o usare le definizioni fortemente tipate esistenti.

Il meccanismo manuale è necessario quando si tenta di implementare una classe che non è stata associata da Xamarin.iOS. È facile eseguire le operazioni seguenti:

  • Contrassegna la classe per la registrazione con il runtime
  • Applicare l'attributo [Export] con il nome del selettore effettivo in ogni metodo di cui si vuole eseguire l'override
  • Creare un'istanza della classe e passarla.

Ad esempio, il codice seguente implementa solo uno dei metodi facoltativi nella definizione del protocollo UIApplicationDelegate:

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

Il Objective-C nome del selettore ("applicationDidFinishLaunching:") viene dichiarato con l'attributo Export e la classe viene registrata con l'attributo [Register] .

Xamarin.iOS fornisce dichiarazioni fortemente tipate, pronte per l'uso, che non richiedono l'associazione manuale. Per supportare questo modello di programmazione, il runtime di Xamarin.iOS supporta l'attributo [Model] in una dichiarazione di classe. In questo modo il runtime informa che non deve collegare tutti i metodi nella classe, a meno che i metodi non vengano implementati in modo esplicito.

Ciò significa che in UIKit le classi che rappresentano un protocollo con metodi facoltativi vengono scritte come segue:

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

Quando si vuole implementare un modello che implementa solo alcuni metodi, è necessario eseguire l'override dei metodi a cui si è interessati e ignorare gli altri metodi. Il runtime associa solo i metodi sovrascritti, non i metodi originali al Objective-C mondo.

L'equivalente all'esempio manuale precedente è:

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

I vantaggi sono che non è necessario esaminare i Objective-C file di intestazione per trovare il selettore, i tipi degli argomenti o il mapping a C# e che si ottiene intellisense da Visual Studio per Mac, insieme ai tipi sicuri

Punti vendita XIB e C#

Si tratta di una descrizione di basso livello del modo in cui Outlet si integra con C# e viene fornito per gli utenti avanzati di Xamarin.iOS. Quando si usa Visual Studio per Mac, il mapping viene eseguito automaticamente in background usando il codice generato in anteprima.

Quando si progetta l'interfaccia utente con Interface Builder, si progetta solo l'aspetto dell'applicazione e si stabiliranno alcune connessioni predefinite. Se si desidera recuperare informazioni a livello di codice, modificare il comportamento di un controllo in fase di esecuzione o modificare il controllo in fase di esecuzione, è necessario associare alcuni dei controlli al codice gestito.

Questa operazione viene eseguita in pochi passaggi:

  1. Aggiungere la dichiarazione di uscita al proprietario del file.
  2. Connessione il controllo al Proprietario del file.
  3. Archiviare l'interfaccia utente più le connessioni nel file XIB/NIB.
  4. Caricare il file NIB in fase di esecuzione.
  5. Accedere alla variabile di uscita.

I passaggi da (1) a (3) sono descritti nella documentazione di Apple per la creazione di interfacce con Interface Builder.

Quando si usa Xamarin.iOS, l'applicazione dovrà creare una classe che deriva da UIViewController. Viene implementato come segue:

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

Quindi, per caricare ViewController da un file NIB, eseguire questa operazione:

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

In questo modo viene caricata l'interfaccia utente dalla scheda di interfaccia di rete. Ora, per accedere agli outlet, è necessario informare il runtime che si vuole accedervi. A tale scopo, la UIViewController sottoclasse deve dichiarare le proprietà e annotarle con l'attributo [Connessione]. nel modo seguente:

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

L'implementazione della proprietà è quella che recupera effettivamente e archivia il valore per il tipo nativo effettivo.

Non è necessario preoccuparsi di questo problema quando si usano Visual Studio per Mac e InterfaceBuilder. Visual Studio per Mac esegue automaticamente il mirroring di tutti gli outlet dichiarati con codice in una classe parziale compilata come parte del progetto.

Selettori

Un concetto di base della Objective-C programmazione è costituito dai selettori. Spesso si verificano API che richiedono di passare un selettore o si prevede che il codice risponda a un selettore.

La creazione di nuovi selettori in C# è semplice: è sufficiente creare una nuova istanza della ObjCRuntime.Selector classe e usare il risultato in qualsiasi posizione nell'API che lo richiede. Ad esempio:

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

Per un metodo C# rispondere a una chiamata del selettore, deve ereditare dal NSObject tipo e il metodo C# deve essere decorato con il nome del selettore usando l'attributo [Export] . Ad esempio:

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

I nomi dei selettori devono corrispondere esattamente, inclusi tutti i due punti intermedi e finali (":"), se presenti.

Costruttori NSObject

La maggior parte delle classi in Xamarin.iOS che derivano da NSObject esporrà costruttori specifici per la funzionalità dell'oggetto, ma esporranno anche vari costruttori che non sono immediatamente evidenti.

I costruttori vengono usati come segue:

public Foo (IntPtr handle)

Questo costruttore viene usato per creare un'istanza della classe quando il runtime deve eseguire il mapping della classe a una classe non gestita. Ciò si verifica quando si carica un file XIB/NIB. A questo punto, il Objective-C runtime avrà creato un oggetto nel mondo non gestito e questo costruttore verrà chiamato per inizializzare il lato gestito.

In genere, è sufficiente chiamare il costruttore di base con il parametro handle e nel corpo eseguire qualsiasi inizializzazione necessaria.

public Foo ()

Questo è il costruttore predefinito per una classe e nelle classi fornite da Xamarin.iOS, inizializza la classe Foundation.NSObject e tutte le classi tra e alla fine, concatena questo al Objective-Cinit metodo sulla classe .

public Foo (NSObjectFlag x)

Questo costruttore viene usato per inizializzare l'istanza, ma impedire al codice di chiamare il Objective-C metodo "init" alla fine. In genere si usa questa operazione quando si è già registrati per l'inizializzazione (quando si usa [Export] nel costruttore) o quando è già stata eseguita l'inizializzazione tramite un'altra media.

public Foo (NSCoder coder)

Questo costruttore viene fornito per i casi in cui l'oggetto viene inizializzato da un'istanza NSCoding.

Eccezioni

La progettazione dell'API Xamarin.iOS non genera Objective-C eccezioni come eccezioni C#. La progettazione impone che nessun garbage venga inviato al Objective-C mondo in primo luogo e che tutte le eccezioni che devono essere prodotte dall'associazione stessa vengano generate prima che i dati non validi vengano mai passati al Objective-C mondo.

Notifications

Sia in iOS che in OS X, gli sviluppatori possono sottoscrivere notifiche trasmesse dalla piattaforma sottostante. A tale scopo, usare il NSNotificationCenter.DefaultCenter.AddObserver metodo . Il AddObserver metodo accetta due parametri, uno è la notifica che si desidera sottoscrivere. L'altro è il metodo da richiamare quando viene generata la notifica.

Sia in Xamarin.iOS che in Xamarin.Mac, le chiavi per le varie notifiche sono ospitate nella classe che attiva le notifiche. Ad esempio, le notifiche generate da UIMenuController sono ospitate come static NSString proprietà nelle UIMenuController classi che terminano con il nome "Notifica".

Gestione della memoria

Xamarin.iOS ha un Garbage Collector che si occupa del rilascio delle risorse quando non sono più in uso. Oltre al Garbage Collector, tutti gli oggetti che derivano dall'implementazione NSObject dell'interfaccia System.IDisposable .

NSObject e IDisposable

L'esposizione dell'interfaccia IDisposable è un modo pratico per aiutare gli sviluppatori a rilasciare oggetti che potrebbero incapsulare grandi blocchi di memoria (ad esempio, un UIImage potrebbe sembrare semplicemente un puntatore innocente, ma potrebbe puntare a un'immagine da 2 megabyte) e altre risorse importanti e finite (come un buffer di decodifica video).

NSObject implementa l'interfaccia IDisposable e anche il modello Dispose .NET. In questo modo gli sviluppatori che sottoclasse NSObject eseguono l'override del comportamento Dispose e rilasciano le proprie risorse su richiesta. Si consideri, ad esempio, questo controller di visualizzazione che mantiene una serie di immagini:

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

Quando un oggetto gestito viene eliminato, non è più utile. Potrebbe essere ancora presente un riferimento agli oggetti, ma l'oggetto è per tutte le finalità e per scopi non validi a questo punto. Alcune API .NET assicurano questa operazione generando un'eccezione ObjectDisposedException se si tenta di accedere a qualsiasi metodo in un oggetto eliminato, ad esempio:

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

Anche se è ancora possibile accedere alla variabile "image", si tratta in realtà di un riferimento non valido e non punta più all'oggetto Objective-C che contiene l'immagine.

Tuttavia, l'eliminazione di un oggetto in C# non significa che l'oggetto verrà necessariamente eliminato definitivamente. Tutto quello che si fa è rilasciare il riferimento che C# ha dovuto all'oggetto. È possibile che l'ambiente Cocoa abbia mantenuto un riferimento per il proprio uso. Ad esempio, se si imposta la proprietà Image di un oggetto UIImageView su un'immagine e quindi si elimina l'immagine, l'oggetto UIImageView sottostante ha preso il proprio riferimento e manterrà un riferimento a questo oggetto fino al termine dell'utilizzo.

Quando chiamare Dispose

Chiama Dispose quando hai bisogno di Mono per sbarazzarsi dell'oggetto. Un possibile caso d'uso è quando Mono non ha alcuna conoscenza del fatto che NSObject contiene effettivamente un riferimento a una risorsa importante come la memoria o un pool di informazioni. In questi casi, è necessario chiamare Dispose per rilasciare immediatamente il riferimento alla memoria, anziché attendere che Mono esegua un ciclo di Garbage Collection.

Internamente, quando Mono crea riferimenti NSString da stringhe C#, li eliminerà immediatamente per ridurre la quantità di lavoro che deve essere eseguita dal Garbage Collector. Minore sarà il numero di oggetti da gestire, più velocemente verrà eseguito il processo GC.

Quando mantenere riferimenti agli oggetti

Un effetto collaterale che la gestione automatica della memoria ha è che il GC eliminerà gli oggetti inutilizzati purché non vi siano riferimenti. Che a volte può avere effetti collaterali sorprendenti, ad esempio, se si crea una variabile locale per contenere il controller di visualizzazione di primo livello, o la finestra di primo livello, e quindi avere quelli svaniti dietro la schiena.

Se non si mantiene un riferimento nelle variabili statiche o di istanza agli oggetti, Mono chiamerà felicemente il metodo Dispose() su di essi e rilascerà il riferimento all'oggetto. Poiché questo potrebbe essere l'unico riferimento in sospeso, il Objective-C runtime eliminerà l'oggetto per l'utente.