Panoramica delle Objective-C associazioni

Dettagli sul funzionamento del processo di associazione

L'associazione di una Objective-C libreria da usare con Xamarin prevede tre passaggi:

  1. Scrivere una "definizione API" C# per descrivere come viene esposta l'API nativa in .NET e come esegue il mapping all'oggetto sottostante Objective-C. Questa operazione viene eseguita usando costrutti C# standard come interface e vari attributi di associazione (vedere questo semplice esempio).

  2. Dopo aver scritto la "definizione API" in C#, compilarla per produrre un assembly "binding". Questa operazione può essere eseguita nella riga di comando o usando un progetto di associazione in Visual Studio per Mac o Visual Studio.

  3. L'assembly di "associazione" viene quindi aggiunto al progetto di applicazione Xamarin, in modo da poter accedere alla funzionalità nativa usando l'API definita. Il progetto di associazione è completamente separato dai progetti dell'applicazione.

    Nota

    Il passaggio 1 può essere automatizzato con l'assistenza di Objective Sharpie. Esamina l'API Objective-C e genera una "definizione API" in C# proposta. È possibile personalizzare i file creati da Objective Sharpie e usarli in un progetto di associazione (o nella riga di comando) per creare l'assembly di associazione. Objective Sharpie non crea associazioni da solo, è semplicemente una parte facoltativa del processo più grande.

È anche possibile leggere altri dettagli tecnici sul funzionamento, che consentono di scrivere i binding.

Associazioni da riga di comando

È possibile usare btouch-native per Xamarin.iOS (o bmac-native se si usa Xamarin.Mac) per compilare direttamente i binding. Funziona passando le definizioni api C# create a mano (o usando Objective Sharpie) allo strumento da riga di comando (btouch-native per iOS o bmac-native per Mac).

La sintassi generale per richiamare questi strumenti è:

# Use this for Xamarin.iOS:
bash$ /Developer/MonoTouch/usr/bin/btouch-native -e cocos2d.cs -s:enums.cs -x:extensions.cs
# Use this for Xamarin.Mac:
bash$ bmac-native -e cocos2d.cs -s:enums.cs -x:extensions.cs

Il comando precedente genererà il file cocos2d.dll nella directory corrente e conterrà la libreria completamente associata che è possibile usare nel progetto. Questo è lo strumento che Visual Studio per Mac usa per creare le associazioni se si usa un progetto di associazione (descritto di seguito).

Progetto di associazione

Un progetto di associazione può essere creato in Visual Studio per Mac o Visual Studio (Visual Studio supporta solo le associazioni iOS) e semplifica la modifica e la compilazione delle definizioni API per l'associazione (rispetto all'uso della riga di comando).

Seguire questa guida introduttiva per informazioni su come creare e usare un progetto di associazione per produrre un'associazione.

Objective Sharpie

Objective Sharpie è un altro strumento da riga di comando separato che consente di eseguire le fasi iniziali della creazione di un'associazione. Non crea un'associazione da sola, ma automatizza il passaggio iniziale di generazione di una definizione API per la libreria nativa di destinazione.

Leggere la documentazione di Objective Sharpie per informazioni su come analizzare librerie native, framework nativi e CocoaPods nelle definizioni API che possono essere integrate in associazioni.

Funzionamento dell'associazione

È possibile usare l'attributo [Register], l'attributo [Export] e la chiamata manuale Objective-C del selettore per associare manualmente i nuovi tipi (in precedenza non associati). Objective-C

Per prima cosa, trovare un tipo da associare. Ai fini della discussione (e semplicità), si associa il tipo N edizione Standard numerator (che è già stato associato in Foundation.N edizione Standard numerator; l'implementazione seguente è solo a scopo di esempio).

In secondo luogo, è necessario creare il tipo C#. È probabile che si voglia inserirlo in uno spazio dei nomi; poiché Objective-C non supporta gli spazi dei nomi, è necessario usare l'attributo [Register] per modificare il nome del tipo che Xamarin.iOS registrerà con il Objective-C runtime. Anche il tipo C# deve ereditare da Foundation.NSObject:

namespace Example.Binding {
    [Register("NSEnumerator")]
    class NSEnumerator : NSObject
    {
        // see steps 3-5
    }
}

In terzo luogo, esaminare la Objective-C documentazione e creare istanze objCRuntime.Selector per ogni selettore che si vuole usare. Inserire questi elementi all'interno del corpo della classe:

static Selector selInit       = new Selector("init");
static Selector selAllObjects = new Selector("allObjects");
static Selector selNextObject = new Selector("nextObject");

Quarto, il tipo dovrà fornire costruttori. È necessario concatenare la chiamata del costruttore al costruttore della classe base. Gli [Export] attributi consentono Objective-C al codice di chiamare i costruttori con il nome del selettore specificato:

[Export("init")]
public NSEnumerator()
    : base(NSObjectFlag.Empty)
{
    Handle = Messaging.IntPtr_objc_msgSend(this.Handle, selInit.Handle);
}
// This constructor must be present so that Xamarin.iOS
// can create instances of your type from Objective-C code.
public NSEnumerator(IntPtr handle)
    : base(handle)
{
}

Quinto, specificare i metodi per ognuno dei selettori dichiarati nel passaggio 3. Verranno usati objc_msgSend() per richiamare il selettore nell'oggetto nativo. Si noti l'uso di Runtime.GetNSObject() per convertire un oggetto IntPtr in un tipo (secondario) tipizzato NSObject in modo appropriato. Se si desidera che il metodo sia chiamabile dal Objective-C codice, il membro deve essere virtuale.

[Export("nextObject")]
public virtual NSObject NextObject()
{
    return Runtime.GetNSObject(
        Messaging.IntPtr_objc_msgSend(this.Handle, selNextObject.Handle));
}
// Note that for properties, [Export] goes on the get/set method:
public virtual NSArray AllObjects {
    [Export("allObjects")]
    get {
        return (NSArray) Runtime.GetNSObject(
            Messaging.IntPtr_objc_msgSend(this.Handle, selAllObjects.Handle));
    }
}

Mettere tutto insieme:

using System;
using Foundation;
using ObjCRuntime;

namespace Example.Binding {
    [Register("NSEnumerator")]
    class NSEnumerator : NSObject
    {
        static Selector selInit       = new Selector("init");
        static Selector selAllObjects = new Selector("allObjects");
        static Selector selNextObject = new Selector("nextObject");

        [Export("init")]
        public NSEnumerator()
            : base(NSObjectFlag.Empty)
        {
            Handle = Messaging.IntPtr_objc_msgSend(this.Handle,
                selInit.Handle);
        }

        public NSEnumerator(IntPtr handle)
            : base(handle)
        {
        }

        [Export("nextObject")]
        public virtual NSObject NextObject()
        {
            return Runtime.GetNSObject(
                Messaging.IntPtr_objc_msgSend(this.Handle,
                    selNextObject.Handle));
        }

        // Note that for properties, [Export] goes on the get/set method:
        public virtual NSArray AllObjects {
            [Export("allObjects")]
            get {
                return (NSArray) Runtime.GetNSObject(
                    Messaging.IntPtr_objc_msgSend(this.Handle,
                        selAllObjects.Handle));
            }
        }
    }
}