Librerie di binding Objective-C
Quando si usa Xamarin.iOS o Xamarin.Mac, è possibile che si verifichino casi in cui si vuole usare una libreria di terze parti Objective-C . In queste situazioni, è possibile usare progetti di associazione Xamarin per creare un'associazione C# alle librerie native Objective-C . Il progetto usa gli stessi strumenti usati per portare le API iOS e Mac in C#.
Questo documento descrive come associare Objective-C le API, se si associano solo LE API C, è consigliabile usare il meccanismo .NET standard per questo, il framework P/Invoke. Informazioni dettagliate su come collegare in modo statico una libreria C sono disponibili nella pagina Collegamento di librerie native.
Vedere la Guida di riferimento ai tipi di associazione complementare. Inoltre, se vuoi saperne di più su cosa accade sotto le quinte, consulta la pagina Panoramica del binding.
Le associazioni possono essere create sia per le librerie iOS che per Mac. Questa pagina descrive come lavorare su un'associazione iOS, tuttavia le associazioni Mac sono molto simili.
Codice di esempio per iOS
È possibile usare il progetto di esempio di binding iOS per sperimentare le associazioni.
Introduzione
Il modo più semplice per creare un'associazione consiste nel creare un progetto di associazione Xamarin.iOS. È possibile eseguire questa operazione da Visual Studio per Mac selezionando il tipo di progetto, libreria di associazioni della libreria >iOS>:
Il progetto generato contiene un piccolo modello che è possibile modificare, contiene due file: ApiDefinition.cs
e StructsAndEnums.cs
.
ApiDefinition.cs
è la posizione in cui si definirà il contratto API, ovvero il file che descrive il modo in cui l'API sottostante Objective-C viene proiettata in C#. La sintassi e il contenuto di questo file sono l'argomento principale della discussione di questo documento e il relativo contenuto è limitato alle interfacce C# e alle dichiarazioni di delegati C#. Il StructsAndEnums.cs
file è il file in cui immettere le definizioni richieste dalle interfacce e dai delegati. Sono inclusi i valori di enumerazione e le strutture che il codice potrebbe usare.
Associazione di un'API
Per eseguire un'associazione completa, è necessario comprendere la definizione dell'API Objective-C e acquisire familiarità con le linee guida per la progettazione di .NET Framework.
Per associare la libreria, in genere si inizierà con un file di definizione DELL'API. Un file di definizione API è semplicemente un file di origine C# che contiene interfacce C# annotate con pochi attributi che consentono di guidare l'associazione. Questo file definisce il contratto tra C# e Objective-C .
Ad esempio, si tratta di un file API semplice per una libreria:
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);
}
}
L'esempio precedente definisce una classe denominata Cocos2D.Camera
che deriva dal NSObject
tipo di base (questo tipo deriva da Foundation.NSObject
) e che definisce una proprietà statica (ZEye
), due metodi che non accettano argomenti e un metodo che accetta tre argomenti.
Una descrizione approfondita del formato del file API e degli attributi che è possibile usare è illustrata nella sezione file di definizione API seguente.
Per produrre un'associazione completa, in genere si gestiscono quattro componenti:
- File di definizione dell'API (
ApiDefinition.cs
nel modello). - Facoltativo: qualsiasi enumerazione, tipi, struct richiesti dal file di definizione dell'API (
StructsAndEnums.cs
nel modello). - Facoltativo: origini aggiuntive che potrebbero espandere l'associazione generata o fornire un'API più intuitiva C# (tutti i file C# aggiunti al progetto).
- Libreria nativa di cui si esegue l'associazione.
Questo grafico mostra la relazione tra i file:
Il file di definizione DELL'API conterrà solo spazi dei nomi e definizioni di interfaccia (con tutti i membri che un'interfaccia può contenere) e non deve contenere classi, enumerazioni, delegati o struct. Il file di definizione dell'API è semplicemente il contratto che verrà usato per generare l'API.
Qualsiasi codice aggiuntivo necessario come enumerazioni o classi di supporto deve essere ospitato in un file separato, nell'esempio precedente "CameraMode" è un valore di enumerazione che non esiste nel file CS e deve essere ospitato in un file separato, ad esempio StructsAndEnums.cs
:
public enum CameraMode {
FlyOver, Back, Follow
}
Il APIDefinition.cs
file viene combinato con la StructsAndEnum
classe e viene usato per generare l'associazione principale della libreria. È possibile usare la libreria risultante così come è, ma in genere si vuole ottimizzare la libreria risultante per aggiungere alcune funzionalità C# a vantaggio degli utenti. Alcuni esempi includono l'implementazione di un ToString()
metodo, fornire indicizzatori C#, aggiungere conversioni implicite da e verso alcuni tipi nativi o fornire versioni fortemente tipizzate di alcuni metodi. Questi miglioramenti vengono archiviati in file C# aggiuntivi. Aggiungere semplicemente i file C# al progetto e verranno inclusi in questo processo di compilazione.
Viene illustrato come implementare il codice nel Extra.cs
file. Si noti che si usano classi parziali perché aumentano le classi parziali generate dalla combinazione di ApiDefinition.cs
e l'associazione StructsAndEnums.cs
principale:
public partial class Camera {
// Provide a ToString method
public override string ToString ()
{
return String.Format ("ZEye: {0}", ZEye);
}
}
La compilazione della libreria produrrà l'associazione nativa.
Per completare questa associazione, è necessario aggiungere la libreria nativa al progetto. A tale scopo, è possibile aggiungere la libreria nativa al progetto trascinando e rilasciando la libreria nativa dal Finder nel progetto in Esplora soluzioni oppure facendo clic con il pulsante destro del mouse sul progetto e scegliendo Aggiungi file> per selezionare la libreria nativa. Le librerie native per convenzione iniziano con la parola "lib" e terminano con l'estensione ".a". Quando si esegue questa operazione, Visual Studio per Mac aggiungerà due file: il file con estensione a e un file C# popolato automaticamente che contiene informazioni su ciò che la libreria nativa contiene:
Il contenuto del libMagicChord.linkwith.cs
file contiene informazioni su come può essere usata questa libreria e indica all'IDE di creare il pacchetto binario nel file DLL risultante:
using System;
using ObjCRuntime;
[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]
Dettagli completi su come usare [LinkWith]
l'attributo è documentato nella Guida di riferimento ai tipi di associazione.
A questo punto, quando si compila il progetto, si finirà con un MagicChords.dll
file che contiene sia l'associazione che la libreria nativa. È possibile distribuire questo progetto o la DLL risultante ad altri sviluppatori per il proprio uso.
In alcuni casi è possibile che siano necessari alcuni valori di enumerazione, definizioni di delegati o altri tipi. Non inserire tali elementi nel file delle definizioni api, perché si tratta semplicemente di un contratto
File di definizione dell'API
Il file di definizione dell'API è costituito da una serie di interfacce. Le interfacce nella definizione dell'API verranno trasformate in una dichiarazione di classe e devono essere decorate con l'attributo [BaseType]
per specificare la classe di base per la classe .
Ci si potrebbe chiedere perché non sono state usate classi anziché interfacce per la definizione del contratto. Sono stati selezionate interfacce perché è stato consentito scrivere il contratto per un metodo senza dover fornire un corpo del metodo nel file di definizione dell'API o dover fornire un corpo che doveva generare un'eccezione o restituire un valore significativo.
Tuttavia, poiché si usa l'interfaccia come scheletro per generare una classe, è necessario ricorrere a decorare varie parti del contratto con attributi per guidare l'associazione.
Metodi di associazione
L'associazione più semplice che è possibile eseguire consiste nell'associare un metodo. È sufficiente dichiarare un metodo nell'interfaccia con le convenzioni di denominazione C# e decorare il metodo con attributo [Export]
. L'attributo [Export]
è il collegamento tra il nome C# e il Objective-C nome nel runtime di Xamarin.iOS. Parametro dell'oggetto [Export]
attribute è il nome del Objective-C selettore. Alcuni esempi:
// 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);
Gli esempi precedenti illustrano come associare i metodi di istanza. Per associare metodi statici, è necessario usare l'attributo [Static]
, come illustrato di seguito:
// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();
Ciò è necessario perché il contratto fa parte di un'interfaccia e le interfacce non hanno alcuna nozione di dichiarazioni statiche e di istanza, pertanto è necessario ricorrere ancora una volta agli attributi. Se si desidera nascondere un metodo specifico dall'associazione, è possibile decorare il metodo con l'attributo [Internal]
.
Il btouch-native
comando introduce controlli per i parametri di riferimento che non sono Null. Se si desidera consentire valori Null per un parametro specifico, usare [NullAllowed]
attributo nel parametro , come illustrato di seguito:
[Export ("setText:")]
string SetText ([NullAllowed] string text);
Quando si esporta un tipo riferimento, con la [Export]
parola chiave è anche possibile specificare la semantica di allocazione. Ciò è necessario per garantire che non vengano persi dati.
Proprietà delle associazioni
Analogamente ai metodi, Objective-C le proprietà vengono associate usando [Export]
attributo ed eseguire il mapping direttamente alle proprietà C#. Proprio come i metodi, le proprietà possono essere decorate con il [Static]
E la [Internal]
Attributi.
Quando si usa l'attributo [Export]
in una proprietà sotto le quinte btouch-native associa effettivamente due metodi: il getter e il setter. Il nome specificato per l'esportazione è il nome di base e il setter viene calcolato anteponendo la parola "set", trasformando la prima lettera del nome base in lettere maiuscole e rendendo il selettore accettare un argomento. Ciò significa che [Export ("label")]
applicato a una proprietà associa effettivamente i metodi "label" e "setLabel:". Objective-C
A volte le Objective-C proprietà non seguono il modello descritto in precedenza e il nome viene sovrascritto manualmente. In questi casi è possibile controllare la modalità di generazione dell'associazione tramite [Bind]
attributo sul getter o setter, ad esempio:
[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }
Viene quindi associato "isMenuVisible" e "setMenuVisible:". Facoltativamente, una proprietà può essere associata usando la sintassi seguente:
[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
[Export ("name")]
string Name();
[Export("setName:")]
void SetName(string name);
}
Dove il getter e il setter sono definiti in modo esplicito come nelle name
associazioni e setName
precedenti.
Oltre al supporto per le proprietà statiche tramite [Static]
, è possibile decorare le proprietà statiche del thread con [IsThreadStatic]
, ad esempio:
[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }
Proprio come i metodi consentono di contrassegnare alcuni parametri con [NullAllowed]
, è possibile applicare [NullAllowed]
a una proprietà per indicare che null è un valore valido per la proprietà, ad esempio:
[Export ("text"), NullAllowed]
string Text { get; set; }
Il [NullAllowed]
parametro può anche essere specificato direttamente nel setter:
[Export ("text")]
string Text { get; [NullAllowed] set; }
Avvertenze dell'associazione di controlli personalizzati
Quando si configura l'associazione per un controllo personalizzato, è necessario considerare le avvertenze seguenti:
- Le proprietà di associazione devono essere statiche : quando si definisce l'associazione delle proprietà, è necessario usare l'attributo
[Static]
. - I nomi delle proprietà devono corrispondere esattamente : il nome utilizzato per associare la proprietà deve corrispondere esattamente al nome della proprietà nel controllo personalizzato.
- I tipi di proprietà devono corrispondere esattamente : il tipo di variabile usato per associare la proprietà deve corrispondere esattamente al tipo della proprietà nel controllo personalizzato.
- Punti di interruzione e getter/setter : i punti di interruzione inseriti nei metodi getter o setter della proprietà non verranno mai raggiunti.
- Osservare i callback: è necessario usare i callback di osservazione per ricevere una notifica delle modifiche nei valori delle proprietà dei controlli personalizzati.
Se non si osserva una delle avvertenze sopra elencate, l'associazione ha esito negativo in modo invisibile all'utente in fase di esecuzione.
Objective-C Modello modificabile e proprietà
Objective-C i framework usano un linguaggio in cui alcune classi non sono modificabili con una sottoclasse modificabile. Ad esempio NSString
, è la versione non modificabile, mentre NSMutableString
è la sottoclasse che consente la mutazione.
In queste classi è comune vedere che la classe base non modificabile contiene proprietà con un getter, ma nessun setter. E per la versione modificabile per introdurre il setter. Poiché ciò non è realmente possibile con C#, è stato necessario eseguire il mapping di questo idioma in un linguaggio che funziona con C#.
Il modo in cui viene eseguito il mapping a C# consiste nell'aggiungere sia il getter che il setter nella classe base, ma contrassegnando il setter con un attributo [NotImplemented]
.
Quindi, nella sottoclasse modificabile, si usa il [Override]
attributo sulla proprietà per assicurarsi che la proprietà stia effettivamente sovrascrivendo il comportamento dell'elemento padre.
Esempio:
[BaseType (typeof (NSObject))]
interface MyTree {
string Name { get; [NotImplemented] set; }
}
[BaseType (typeof (MyTree))]
interface MyMutableTree {
[Override]
string Name { get; set; }
}
Costruttori di binding
Lo btouch-native
strumento genererà automaticamente quattro costruttori nella classe, per una determinata classe Foo
, che genera:
Foo ()
: costruttore predefinito (mappa al Objective-Ccostruttore "init")Foo (NSCoder)
: costruttore usato durante la deserializzazione dei file NIB (esegue il mapping al Objective-Ccostruttore "initWithCoder:".Foo (IntPtr handle)
: il costruttore per la creazione basata su handle, viene richiamato dal runtime quando il runtime deve esporre un oggetto gestito da un oggetto non gestito.Foo (NSEmptyFlag)
: viene usato dalle classi derivate per impedire l'inizializzazione doppia.
Per i costruttori definiti, devono essere dichiarati usando la firma seguente all'interno della definizione dell'interfaccia: devono restituire un IntPtr
valore e il nome del metodo deve essere Costruttore. Ad esempio, per associare il initWithFrame:
costruttore, si tratta di ciò che si userebbe:
[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);
Protocolli di associazione
Come descritto nel documento di progettazione dell'API, nella sezione che illustra modelli e protocolli, Xamarin.iOS esegue il mapping dei Objective-C protocolli in classi contrassegnate con il attributo [Model]
. Questo viene in genere usato quando si implementano Objective-C classi delegate.
La grande differenza tra una classe associata regolare e una classe delegato è che la classe delegato potrebbe avere uno o più metodi facoltativi.
Si consideri ad esempio la UIKit
classe UIAccelerometerDelegate
, come è associata in Xamarin.iOS:
[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
[Export ("accelerometer:didAccelerate:")]
void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}
Poiché si tratta di un metodo facoltativo nella definizione per UIAccelerometerDelegate
non c'è altro da fare. Tuttavia, se è presente un metodo obbligatorio nel protocollo, è necessario aggiungere [Abstract]
attributo al metodo . In questo modo l'utente dell'implementazione fornirà effettivamente un corpo per il metodo .
In generale, i protocolli vengono usati nelle classi che rispondono ai messaggi. Questa operazione viene in genere eseguita in Objective-C assegnando alla proprietà "delegato" un'istanza di un oggetto che risponde ai metodi nel protocollo.
La convenzione in Xamarin.iOS consiste nel supportare sia lo Objective-C stile ad accoppiamento libero in cui qualsiasi istanza di un NSObject
oggetto può essere assegnata al delegato, sia per esporre una versione fortemente tipizzata. Per questo motivo, in genere viene fornita sia una Delegate
proprietà fortemente tipizzata che un WeakDelegate
oggetto tipizzato in modo libero. La versione tipizzata viene in genere associata a [Export]
e viene usato l'attributo [Wrap]
per fornire la versione fortemente tipizzata.
In questo modo viene illustrato come è stata associata la UIAccelerometer
classe :
[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; }
}
Novità di MonoTouch 7.0
A partire da MonoTouch 7.0 è stata incorporata una funzionalità di associazione di protocollo nuova e migliorata. Questo nuovo supporto semplifica l'uso Objective-C di idiomi per l'adozione di uno o più protocolli in una determinata classe.
Per ogni definizione MyProtocol
di protocollo in Objective-Cè ora disponibile un'interfaccia IMyProtocol
che elenca tutti i metodi richiesti dal protocollo, nonché una classe di estensione che fornisce tutti i metodi facoltativi. L'esempio precedente, combinato con il nuovo supporto nell'editor di Xamarin Studio, consente agli sviluppatori di implementare metodi di protocollo senza dover usare le sottoclassi separate delle classi di modelli astratte precedenti.
Qualsiasi definizione che contiene l'attributo [Protocol]
genererà effettivamente tre classi di supporto che migliorano notevolmente il modo in cui si utilizzano i protocolli:
// 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);
}
}
L'implementazione della classe fornisce una classe astratta completa che è possibile eseguire l'override dei singoli metodi di e ottenere la sicurezza completa dei tipi. Tuttavia, a causa di C# che non supporta l'ereditarietà multipla, esistono scenari in cui potrebbe essere necessario avere una classe base diversa, ma si vuole comunque implementare un'interfaccia, ovvero la posizione in cui è
Viene fornita la definizione dell'interfaccia generata. Si tratta di un'interfaccia che dispone di tutti i metodi necessari dal protocollo. Ciò consente agli sviluppatori che vogliono implementare il protocollo per implementare semplicemente l'interfaccia . Il runtime registrerà automaticamente il tipo come adozione del protocollo.
Si noti che l'interfaccia elenca solo i metodi richiesti ed espone i metodi facoltativi. Ciò significa che le classi che adottano il protocollo otterranno il controllo completo dei tipi per i metodi richiesti, ma dovranno ricorrere alla digitazione debole (usando [Export]
manualmente gli attributi e la corrispondenza della firma) per i metodi di protocollo facoltativi.
Per semplificare l'utilizzo di un'API che usa protocolli, lo strumento di associazione produrrà anche una classe di metodi di estensione che espone tutti i metodi facoltativi. Ciò significa che, purché si stia usando un'API, sarà possibile considerare i protocolli come tutti i metodi.
Se si vogliono usare le definizioni di protocollo nell'API, è necessario scrivere le interfacce vuote nella definizione dell'API. Se si vuole usare MyProtocol in un'API, è necessario eseguire questa operazione:
[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 ();
}
Il codice precedente è necessario perché in fase di associazione l'oggetto IMyProtocol
non esiste, ecco perché è necessario fornire un'interfaccia vuota.
Adozione di interfacce generate dal protocollo
Ogni volta che si implementa una delle interfacce generate per i protocolli, come illustrato di seguito:
class MyDelegate : NSObject, IUITableViewDelegate {
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
L'implementazione per i metodi di interfaccia necessari viene esportata con il nome corretto, pertanto equivale a questo:
class MyDelegate : NSObject, IUITableViewDelegate {
[Export ("getRowHeight:")]
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
Questo funzionerà per tutti i membri del protocollo necessari, ma esiste un caso speciale con selettori facoltativi di cui tenere conto. I membri del protocollo facoltativi vengono trattati in modo identico quando si usa la classe base:
public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
ma quando si usa l'interfaccia del protocollo è necessario aggiungere [Export]. L'IDE lo aggiungerà tramite completamento automatico quando lo si aggiunge a partire dall'override.
public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
Esiste una leggera differenza di comportamento tra i due in fase di esecuzione.
- Gli utenti della classe base (NSUrlSessionDownloadDelegate, ad esempio) forniscono tutti i selettori obbligatori e facoltativi, restituendo valori predefiniti ragionevoli.
- Gli utenti dell'interfaccia (INSUrlSessionDownloadDelegate in esempio) rispondono solo ai selettori esatti forniti.
Alcune classi rare possono comportarsi in modo diverso qui. In quasi tutti i casi, tuttavia, è sicuro usarlo.
Estensioni della classe di associazione
In Objective-C è possibile estendere le classi con nuovi metodi, simili in spirito ai metodi di estensione di C#. Quando uno di questi metodi è presente, è possibile usare [BaseType]
attributo per contrassegnare il metodo come destinatario del Objective-C messaggio.
Ad esempio, in Xamarin.iOS sono stati associati i metodi di estensione definiti NSString
in quando UIKit
vengono importati come metodi in NSStringDrawingExtensions
, come illustrato di seguito:
[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
[Export ("drawAtPoint:withFont:")]
CGSize DrawString (CGPoint point, UIFont font);
}
Elenchi di argomenti di associazione Objective-C
Objective-C supporta argomenti variadic. Ad esempio:
- (void) appendWorkers:(XWorker *) firstWorker, ...
NS_REQUIRES_NIL_TERMINATION ;
Per richiamare questo metodo da C# è necessario creare una firma simile alla seguente:
[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)
Questo dichiara il metodo come interno, nascondendo l'API precedente dagli utenti, ma esponendola alla libreria. È quindi possibile scrivere un metodo simile al seguente:
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);
}
Campi di associazione
In alcuni casi è necessario accedere ai campi pubblici dichiarati in una libreria.
In genere questi campi contengono stringhe o valori interi a cui è necessario fare riferimento. Vengono comunemente usati come stringa che rappresentano una notifica specifica e come chiavi nei dizionari.
Per associare un campo, aggiungere una proprietà al file di definizione dell'interfaccia e decorare la proprietà con l'attributo [Field]
. Questo attributo accetta un parametro: il nome C del simbolo da cercare. Ad esempio:
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
Se si desidera eseguire il wrapping di vari campi in una classe statica che non deriva da NSObject
, è possibile usare [Static]
attributo nella classe , come illustrato di seguito:
[Static]
interface LonelyClass {
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
}
Il codice precedente genererà un oggetto LonelyClass
che non deriva da NSObject
e conterrà un'associazione all'oggetto NSSomeEventNotification
NSString
esposto come NSString
.
L'attributo [Field]
può essere applicato ai tipi di dati seguenti:
NSString
riferimenti (solo proprietà di sola lettura)NSArray
riferimenti (solo proprietà di sola lettura)- Int a 32 bit (
System.Int32
) - Ints a 64 bit (
System.Int64
) - Float a 32 bit (
System.Single
) - Float a 64 bit (
System.Double
) System.Drawing.SizeF
CGSize
Oltre al nome del campo nativo, è possibile specificare il nome della libreria in cui si trova il campo passando il nome della libreria:
[Static]
interface LonelyClass {
[Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
NSString SomeSharedLibrarySymbol { get; }
}
Se si esegue il collegamento statico, non esiste alcuna libreria a cui eseguire l'associazione, quindi è necessario usare il __Internal
nome:
[Static]
interface LonelyClass {
[Field ("MyFieldFromALibrary", "__Internal")]
NSString MyFieldFromALibrary { get; }
}
Enumerazioni di binding
È possibile aggiungere enum
direttamente nei file di associazione per semplificare l'uso all'interno delle definizioni API, senza usare un file di origine diverso (che deve essere compilato sia nelle associazioni che nel progetto finale).
Esempio:
[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}
interface MyType {
[Export ("initWithEnum:")]
IntPtr Constructor (MyEnum value);
}
È anche possibile creare enumerazioni personalizzate per sostituire NSString
le costanti. In questo caso, il generatore creerà automaticamente i metodi per convertire automaticamente i valori di enumerazione e le costanti NSString.
Esempio:
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);
}
Nell'esempio precedente è possibile decidere di decorare void Perform (NSString mode);
con un [Internal]
attributo . In questo modo l'API basata su costante verrà nascosta ai consumer di binding.
Tuttavia, questo limiterebbe la sottoclasse del tipo come alternativa api più bella usa un [Wrap]
attributo . Questi metodi generati non virtual
sono , ad esempio non sarà possibile eseguirne l'override, che potrebbe, o no, essere una scelta valida.
Un'alternativa consiste nel contrassegnare la definizione originale, NSString
basata su , come [Protected]
. Ciò consentirà il funzionamento della sottoclasse, quando necessario e la versione wrap'ed funzionerà comunque e chiamerà il metodo di override.
Associazione NSValue
di , NSNumber
e NSString
a un tipo migliore
L'attributo consente l'associazione [BindAs]
NSNumber
di e NSValue
(enumerazioni NSString
) in tipi C# più accurati. L'attributo può essere usato per creare un'API .NET più accurata e più accurata tramite l'API nativa.
È possibile decorare i metodi (sul valore restituito), i parametri e le proprietà con [BindAs]
.
L'unica restrizione è che il membro non deve trovarsi all'interno di un [Protocol]
o [Model]
interfaccia.
Ad esempio:
[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);
Restituirebbe:
[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }
Internamente faremo le bool?
conversioni ->NSNumber
e CGRect
<->NSValue
.<
[BindAs]
supporta anche matrici di NSNumber
NSValue
e NSString
(enumerazioni).
Ad esempio:
[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }
Restituirebbe:
[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }
CAScroll
è un'enumerazione NSString
supportata, recupereremo il valore corretto NSString
e gestiremo la conversione del tipo.
Vedere la [BindAs]
documentazione per visualizzare i tipi di conversione supportati.
Notifiche di associazione
Le notifiche sono messaggi inviati a NSNotificationCenter.DefaultCenter
e vengono usati come meccanismo per trasmettere messaggi da una parte dell'applicazione a un'altra. Gli sviluppatori sottoscrivono in genere le notifiche usando il metodo AddObserver di NSNotificationCenter. Quando un'applicazione invia un messaggio al Centro notifiche, in genere contiene un payload archiviato nel dizionario NSNotification.UserInfo . Questo dizionario viene digitato in modo debole e le informazioni sono soggette a errori, perché gli utenti in genere devono leggere nella documentazione le chiavi disponibili nel dizionario e i tipi di valori che possono essere archiviati nel dizionario. La presenza di chiavi a volte viene usata anche come valore booleano.
Il generatore di associazioni Xamarin.iOS fornisce supporto per gli sviluppatori per associare le notifiche. A tale scopo, impostare [Notification]
attributo su una proprietà contrassegnata anche con un oggetto [Field]
proprietà (può essere pubblica o privata).
Questo attributo può essere usato senza argomenti per le notifiche che non contengono payload oppure è possibile specificare un System.Type
oggetto che fa riferimento a un'altra interfaccia nella definizione dell'API, in genere con il nome che termina con "EventArgs". Il generatore trasformerà l'interfaccia in una classe che sottoclassi EventArgs
e includerà tutte le proprietà elencate. L'attributo [Export]
deve essere usato nella classe EventArgs per elencare il nome della chiave usata per cercare il Objective-C dizionario per recuperare il valore.
Ad esempio:
interface MyClass {
[Notification]
[Field ("MyClassDidStartNotification")]
NSString DidStartNotification { get; }
}
Il codice precedente genererà una classe MyClass.Notifications
annidata con i metodi seguenti:
public class MyClass {
[..]
public Notifications {
public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
}
}
Gli utenti del codice possono quindi sottoscrivere facilmente le notifiche inviate a NSDefaultCenter usando codice simile al seguente:
var token = MyClass.Notifications.ObserverDidStart ((notification) => {
Console.WriteLine ("Observed the 'DidStart' event!");
});
Il valore restituito da ObserveDidStart
può essere usato per interrompere facilmente la ricezione di notifiche, come illustrato di seguito:
token.Dispose ();
In alternativa, è possibile chiamare NSNotification.DefaultCenter.RemoveObserver e passare il token. Se la notifica contiene parametri, è necessario specificare un'interfaccia helper EventArgs
, come illustrato di seguito:
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; }
}
Il codice precedente genererà una MyScreenChangedEventArgs
classe con le ScreenX
proprietà e ScreenY
che recupererà i dati dal dizionario NSNotification.UserInfo usando rispettivamente le chiavi "ScreenXKey" e "ScreenYKey" e applicherà le conversioni appropriate. L'attributo [ProbePresence]
viene usato per il generatore per eseguire il probe se la chiave è impostata in UserInfo
, anziché tentare di estrarre il valore. Viene usato per i casi in cui la presenza della chiave è il valore (in genere per i valori booleani).
In questo modo è possibile scrivere codice simile al seguente:
var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});
Categorie di associazione
Le categorie sono un Objective-C meccanismo usato per estendere il set di metodi e proprietà disponibili in una classe. In pratica, vengono usati per estendere la funzionalità di una classe base (ad esempio ) quando un framework specifico è collegato in (ad esempio NSObject
UIKit
), rendendo disponibili i relativi metodi, ma solo se il nuovo framework è collegato. In altri casi, vengono usati per organizzare le funzionalità in una classe in base alle funzionalità. Sono simili ai metodi di estensione C#. Questa è l'aspetto di una categoria in Objective-C:
@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end
L'esempio precedente se trovato in una libreria estende le istanze di UIView
con il metodo makeBackgroundRed
.
Per associarli, è possibile usare l'attributo [Category]
in una definizione di interfaccia. Quando si usa [Category]
attributo, il significato del [BaseType]
modifiche dell'attributo da usare per specificare la classe di base da estendere, in modo che sia il tipo da estendere.
Di seguito viene illustrato come le UIView
estensioni sono associate e trasformate in metodi di estensione C#:
[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
[Export ("makeBackgroundRed")]
void MakeBackgroundRed ();
}
Il codice precedente creerà una MyUIViewExtension
classe che contiene il MakeBackgroundRed
metodo di estensione. Ciò significa che è ora possibile chiamare "MakeBackgroundRed" in qualsiasi UIView
sottoclasse, offrendo la stessa funzionalità che si otterrebbe su Objective-C. In altri casi, le categorie vengono usate non per estendere una classe di sistema, ma per organizzare le funzionalità, esclusivamente a scopo decorativo. nel modo seguente:
@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end
@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end
Anche se è possibile usare [Category]
attributo anche per questo stile di decorazione di dichiarazioni, è anche possibile aggiungerli tutti alla definizione della classe. Entrambe le operazioni otterrebbero lo stesso risultato:
[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);
}
In questi casi è più breve unire le categorie:
[BaseType (typeof (NSObject))]
interface SocialNetworking {
[Export ("postToTwitter:")]
void PostToTwitter (Message message);
[Export ("postToFacebook:andPicture:")]
void PostToFacebook (Message message, UIImage picture);
}
Blocchi di associazione
I blocchi sono un nuovo costrutto introdotto da Apple per portare l'equivalente funzionale dei metodi anonimi C# a Objective-C. Ad esempio, la NSSet
classe espone ora questo metodo:
- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block
La descrizione precedente dichiara un metodo denominato enumerateObjectsUsingBlock:
che accetta un argomento denominato block
. Questo blocco è simile a un metodo anonimo C# in quanto supporta l'acquisizione dell'ambiente corrente (il puntatore "this", l'accesso a variabili e parametri locali). Il metodo precedente in NSSet
richiama il blocco con due parametri ( NSObject
la id obj
parte) e un puntatore a un valore booleano (la BOOL *stop
parte ).
Per associare questo tipo di API con btouch, è prima necessario dichiarare la firma del tipo di blocco come delegato C# e quindi farvi riferimento da un punto di ingresso dell'API, come illustrato di seguito:
// 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)
Ora il codice può chiamare la funzione da 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);
});
È anche possibile usare espressioni lambda se si preferisce:
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);
});
Metodi asincroni
Il generatore di associazioni può trasformare una determinata classe di metodi in metodi asincroni (metodi che restituiscono un oggetto Task o Task<T>).
È possibile usare il [Async]
attributo sui metodi che restituiscono void e l'ultimo argomento è un callback. Quando si applica a un metodo, il generatore di associazioni genererà una versione di tale metodo con il suffisso Async
. Se il callback non accetta parametri, il valore restituito sarà , Task
se il callback accetta un parametro, il risultato sarà .Task<T>
Se il callback accetta più parametri, è necessario impostare ResultType
o ResultTypeName
per specificare il nome desiderato del tipo generato che conterrà tutte le proprietà.
Esempio:
[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);
Il codice precedente genererà sia il metodo LoadFile che:
[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);
Visualizzazione di tipi sicuri per i parametri NSDictionary deboli
In molte posizioni nell'API Objective-C , i parametri vengono passati come API con tipizzati deboli NSDictionary
con chiavi e valori specifici, ma questi sono soggetti a errori (è possibile passare chiavi non valide e non ricevere avvisi; è possibile passare valori non validi e non ricevere avvisi) e frustrante da usare perché richiedono più viaggi alla documentazione per cercare i possibili nomi e valori di chiave.
La soluzione consiste nel fornire una versione fortemente tipizzata che fornisce la versione fortemente tipizzata dell'API e dietro le quinte esegue il mapping delle varie chiavi e valori sottostanti.
Ad esempio, se l'API Objective-C ha accettato un NSDictionary
oggetto e viene documentato come accettare la chiave XyzVolumeKey
che accetta un NSNumber
oggetto con un valore di volume compreso tra 0,0 e 1.0 e che XyzCaptionKey
accetta una stringa, si vuole che gli utenti abbiano un'API interessante simile alla seguente:
public class XyzOptions {
public nfloat? Volume { get; set; }
public string Caption { get; set; }
}
La Volume
proprietà è definita come float nullable, perché la convenzione in Objective-C non richiede che questi dizionari abbiano il valore, quindi esistono scenari in cui il valore potrebbe non essere impostato.
A tale scopo, è necessario eseguire alcune operazioni:
- Creare una classe fortemente tipizzata, che sottoclassi DictionaryContainer e fornisce i vari getter e setter per ogni proprietà.
- Dichiara gli overload per i metodi che accettano
NSDictionary
la nuova versione fortemente tipizzata.
È possibile creare manualmente la classe fortemente tipizzata oppure usare il generatore per eseguire automaticamente il lavoro. Si esaminerà prima di tutto come eseguire questa operazione manualmente, in modo da comprendere cosa sta succedendo e quindi l'approccio automatico.
È necessario creare un file di supporto per questo, ma non viene inserito nell'API del contratto. Questo è ciò che è necessario scrivere per creare la classe 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
}
È quindi necessario fornire un metodo wrapper che espone l'API di alto livello, oltre all'API di basso livello.
[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);
}
Se l'API non deve essere sovrascritta, è possibile nascondere in modo sicuro l'API basata su NSDictionary usando attributo [Internal]
.
Come si può notare, usiamo il [Wrap]
attributo per visualizzare un nuovo punto di ingresso DELL'API ed è stato illustrato usando la classe fortemente tipizzata XyzOptions
. Il metodo wrapper consente anche di passare null.
Ora, una cosa che non abbiamo menzionato è dove provengono i XyzOptionsKeys
valori. In genere si raggruppano le chiavi visualizzate da un'API in una classe statica come XyzOptionsKeys
, come illustrato di seguito:
[Static]
class XyzOptionKeys {
[Field ("kXyzVolumeKey")]
NSString VolumeKey { get; }
[Field ("kXyzCaptionKey")]
NSString CaptionKey { get; }
}
Esaminiamo il supporto automatico per la creazione di questi dizionari fortemente tipizzato. In questo modo si evita un sacco di boilerplate ed è possibile definire il dizionario direttamente nel contratto API, invece di usare un file esterno.
Per creare un dizionario fortemente tipizzato, introdurre un'interfaccia nell'API e decorarla con l'attributo StrongDictionary . In questo modo, il generatore deve creare una classe con lo stesso nome dell'interfaccia da cui derivare DictionaryContainer
e fornirà funzioni di accesso fortemente tipizzate.
L'attributo [StrongDictionary]
accetta un parametro, ovvero il nome della classe statica che contiene le chiavi del dizionario. Ogni proprietà dell'interfaccia diventerà quindi una funzione di accesso fortemente tipizzata. Per impostazione predefinita, il codice userà il nome della proprietà con il suffisso "Key" nella classe statica per creare la funzione di accesso.
Ciò significa che la creazione della funzione di accesso fortemente tipizzata non richiede più un file esterno, né la necessità di creare manualmente getter e setter per ogni proprietà, né dover cercare manualmente le chiavi.
L'intera associazione avrà un aspetto simile al seguente:
[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);
}
Nel caso in cui sia necessario fare riferimento nei XyzOption
membri un campo diverso (che non è il nome della proprietà con il suffisso Key
), è possibile decorare la proprietà con un [Export]
attributo con il nome che si vuole usare.
Mapping dei tipi
Questa sezione illustra come Objective-C viene eseguito il mapping dei tipi ai tipi C#.
Tipi semplici
La tabella seguente illustra come eseguire il Objective-C mapping dei tipi dal mondo CocoaTouch a Xamarin.iOS:
Objective-C nome del tipo | Tipo di API unificata Xamarin.iOS |
---|---|
BOOL , GLboolean |
bool |
NSInteger |
nint |
NSUInteger |
nuint |
CFTimeInterval / NSTimeInterval |
double |
NSString (altre informazioni sull'associazione NSString) |
string |
char * |
string (vedere anche: [PlainString] ) |
CGRect |
CGRect |
CGPoint |
CGPoint |
CGSize |
CGSize |
CGFloat , GLfloat |
nfloat |
Tipi CoreFoundation (CF* ) |
CoreFoundation.CF* |
GLint |
nint |
GLfloat |
nfloat |
Tipi di base (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 |
Matrici
Il runtime di Xamarin.iOS si occupa automaticamente della conversione delle matrici C# in NSArrays
e dell'esecuzione della conversione, quindi, ad esempio, il metodo immaginario Objective-C che restituisce un NSArray
di UIViews
:
// Get the peer views - untyped
- (NSArray *)getPeerViews ();
// Set the views for this container
- (void) setViews:(NSArray *) views
È associato come segue:
[Export ("getPeerViews")]
UIView [] GetPeerViews ();
[Export ("setViews:")]
void SetViews (UIView [] views);
L'idea consiste nell'usare una matrice C# fortemente tipizzata, in quanto ciò consentirà all'IDE di fornire il completamento corretto del codice con il tipo effettivo senza forzare l'utente a indovinare o cercare la documentazione per individuare il tipo effettivo degli oggetti contenuti nella matrice.
Nei casi in cui non è possibile rilevare il tipo più derivato effettivo contenuto nella matrice, è possibile usare NSObject []
come valore restituito.
Selettori
I selettori vengono visualizzati nell'API Objective-C come tipo SEL
speciale . Quando si associa un selettore, si esegue il mapping del tipo a ObjCRuntime.Selector
. In genere, i selettori vengono esposti in un'API con un oggetto, l'oggetto di destinazione e un selettore da richiamare nell'oggetto di destinazione. Fornire entrambi questi elementi corrisponde fondamentalmente al delegato C#: un elemento che incapsula sia il metodo da richiamare che l'oggetto in cui richiamare il metodo.
Questo è l'aspetto dell'associazione:
interface Button {
[Export ("setTarget:selector:")]
void SetTarget (NSObject target, Selector sel);
}
Questo è il modo in cui il metodo viene in genere usato in un'applicazione:
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
b.SetTarget (this, new Selector ("print"));
}
[Export ("print")]
void ThePrintMethod ()
{
// This does the printing
}
}
Per rendere più adatta l'associazione agli sviluppatori C#, in genere si fornirà un metodo che accetta un NSAction
parametro, che consente l'uso di delegati e espressioni lambda C# anziché .Target+Selector
A tale scopo, in genere si nasconde il SetTarget
metodo contrassegnandolo con un [Internal]
attributo e quindi si espone un nuovo metodo helper, come illustrato di seguito:
// 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);
}
}
Ora il codice utente può essere scritto come segue:
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
// First Style
b.SetTarget (ThePrintMethod);
// Lambda style
b.SetTarget (() => { /* print here */ });
}
void ThePrintMethod ()
{
// This does the printing
}
}
Stringhe
Quando si associa un metodo che accetta , NSString
è possibile sostituirlo con un tipo stringa C#, sia per i tipi restituiti che per i parametri.
L'unico caso in cui si potrebbe voler usare direttamente è NSString
quando la stringa viene usata come token. Per altre informazioni sulle stringhe e NSString
, vedere il documento Progettazione API in NSString .
In alcuni casi rari, un'API potrebbe esporre una stringa simile a C (char *
) anziché una Objective-C stringa (NSString *
). In questi casi, è possibile annotare il parametro con attributo [PlainString]
.
parametri out/ref
Alcune API restituiscono valori nei relativi parametri o passano parametri per riferimento.
In genere la firma è simile alla seguente:
- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref
Il primo esempio mostra un linguaggio comune Objective-C per restituire i codici di errore, un puntatore a un puntatore a un NSError
puntatore e, al momento della restituzione, viene impostato il valore. Il secondo metodo mostra come un Objective-C metodo può accettare un oggetto e modificarne il contenuto. Si tratta di un passaggio per riferimento, anziché di un valore di output puro.
Il binding sarà simile al seguente:
[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);
Attributi di gestione della memoria
Quando si usa l'attributo [Export]
e si passano dati che verranno conservati dal metodo chiamato, è possibile specificare la semantica dell'argomento passandola come secondo parametro, ad esempio:
[Export ("method", ArgumentSemantic.Retain)]
L'oggetto precedente contrassegna il valore come con la semantica "Mantieni". La semantica disponibile è:
- Assegnazione
- Copia
- Mantenere
Linee guida per lo stile
Uso di [interno]
È possibile usare il [Internal]
attributo per nascondere un metodo dall'API pubblica. È possibile eseguire questa operazione nei casi in cui l'API esposta è troppo bassa e si vuole fornire un'implementazione di alto livello in un file separato basato su questo metodo.
È anche possibile usarlo quando si verificano limitazioni nel generatore di associazioni, ad esempio alcuni scenari avanzati potrebbero esporre tipi non associati e si vuole eseguire il binding in modo personalizzato e si vuole eseguire il wrapping di tali tipi in modo personalizzato.
Gestori eventi e callback
Objective-C le classi in genere trasmettono notifiche o richiedono informazioni inviando un messaggio su una classe delegato (Objective-C delegato).
Questo modello, sebbene completamente supportato e risolto da Xamarin.iOS può talvolta risultare complesso. Xamarin.iOS espone il modello di evento C# e un sistema di callback del metodo nella classe che può essere usata in queste situazioni. In questo modo è possibile eseguire codice simile al seguente:
button.Clicked += delegate {
Console.WriteLine ("I was clicked");
};
Il generatore di associazioni è in grado di ridurre la quantità di tipizzazione necessaria per eseguire il mapping del Objective-C modello nel modello C#.
A partire da Xamarin.iOS 1.4 sarà anche possibile indicare al generatore di produrre associazioni per un delegato specifico Objective-C ed esporre il delegato come eventi e proprietà C# nel tipo host.
Esistono due classi coinvolte in questo processo, la classe host che sarà quella che attualmente genera eventi e li invia nella Delegate
classe delegato o WeakDelegate
e nella classe delegato effettiva.
Considerando la configurazione seguente:
[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);
}
Per eseguire il wrapping della classe è necessario:
- Nella classe host aggiungere al
[BaseType]
dichiarazione del tipo che funge da delegato e del nome C# esposto. Nell'esempio precedente sonotypeof (MyClassDelegate)
eWeakDelegate
rispettivamente. - Nella classe del delegato, in ogni metodo con più di due parametri, è necessario specificare il tipo da usare per la classe EventArgs generata automaticamente.
Il generatore di associazioni non è limitato al wrapping solo di una singola destinazione evento, è possibile che alcune Objective-C classi emettano messaggi a più delegati, quindi è necessario fornire matrici per supportare questa configurazione. La maggior parte delle configurazioni non sarà necessaria, ma il generatore è pronto per supportare tali casi.
Il codice risultante sarà:
[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);
}
Viene EventArgs
utilizzato per specificare il nome della EventArgs
classe da generare. È consigliabile usare uno per firma (in questo esempio, EventArgs
conterrà una With
proprietà di tipo nint).
Con le definizioni precedenti, il generatore produrrà l'evento seguente nella classe MyClass generata:
public MyClassLoadedEventArgs : EventArgs {
public MyClassLoadedEventArgs (nint bytes);
public nint Bytes { get; set; }
}
public event EventHandler<MyClassLoadedEventArgs> Loaded {
add; remove;
}
È quindi possibile usare il codice come segue:
MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};
I callback sono esattamente come le chiamate agli eventi, la differenza è che invece di avere più sottoscrittori potenziali (ad esempio, più metodi possono eseguire l'hook in un Clicked
evento o un DownloadFinished
evento) i callback possono avere un solo sottoscrittore.
Il processo è identico, l'unica differenza è che invece di esporre il nome della EventArgs
classe che verrà generata, eventArgs viene effettivamente usato per assegnare un nome al delegato C# risultante.
Se il metodo nella classe delegate restituisce un valore, il generatore di associazioni eseguirà il mapping di questo oggetto in un metodo delegato nella classe padre anziché in un evento. In questi casi è necessario specificare il valore predefinito che deve essere restituito dal metodo se l'utente non si collega al delegato. A tale scopo, usare [DefaultValue]
o [DefaultValueFromArgument]
attributi.
[DefaultValue]
hardcodederà un valore restituito, mentre [DefaultValueFromArgument]
viene utilizzato per specificare quale argomento di input verrà restituito.
Enumerazioni e tipi di base
È anche possibile fare riferimento a enumerazioni o tipi di base non direttamente supportati dal sistema di definizione dell'interfaccia btouch. A tale scopo, inserire le enumerazioni e i tipi di core in un file separato e includerlo come parte di uno dei file aggiuntivi forniti per il ritocco.
Collegamento delle dipendenze
Se si associano API che non fanno parte dell'applicazione, è necessario assicurarsi che il file eseguibile sia collegato a queste librerie.
È necessario informare Xamarin.iOS su come collegare le librerie, questa operazione può essere eseguita modificando la configurazione della compilazione per richiamare il mtouch
comando con alcuni argomenti di compilazione aggiuntivi che specificano come collegare le nuove librerie usando l'opzione "-gcc_flags", seguita da una stringa tra virgolette che contiene tutte le librerie aggiuntive necessarie per il programma. Così:
-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"
L'esempio precedente collega libMyLibrary.a
e libSystemLibrary.dylib
la libreria framework all'eseguibile CFNetwork
finale.
In alternativa, è possibile sfruttare il livello [LinkWithAttribute]
di assembly , che è possibile incorporare nei file di contratto , ad esempio AssemblyInfo.cs
.
Quando si usa [LinkWithAttribute]
, sarà necessario avere la libreria nativa disponibile al momento dell'associazione, in quanto incorporare la libreria nativa con l'applicazione. Ad esempio:
// 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)]
Ci si potrebbe chiedere, perché è necessario -force_load
il comando e il motivo è che il flag -ObjC anche se compila il codice in, non mantiene i metadati necessari per supportare le categorie (l'eliminazione del linker/compilatore dead code strips) che è necessario in fase di esecuzione per Xamarin.iOS.
Riferimenti assistito
Alcuni oggetti temporanei come fogli di azione e caselle di avviso sono complessi da tenere traccia per gli sviluppatori e il generatore di associazioni può aiutare un po 'qui.
Ad esempio, se si dispone di una classe che mostrava un messaggio e quindi generava un Done
evento, il modo tradizionale di gestire questo comportamento sarebbe:
class Demo {
MessageBox box;
void ShowError (string msg)
{
box = new MessageBox (msg);
box.Done += { box = null; ... };
}
}
Nello scenario precedente lo sviluppatore deve mantenere il riferimento all'oggetto stesso e perdere o cancellare attivamente il riferimento per box autonomamente. Mentre il codice di associazione, il generatore supporta tenere traccia del riferimento e cancellarlo quando viene richiamato un metodo speciale, il codice precedente diventa quindi:
class Demo {
void ShowError (string msg)
{
var box = new MessageBox (msg);
box.Done += { ... };
}
}
Si noti che non è più necessario mantenere la variabile in un'istanza di , che funziona con una variabile locale e che non è necessario cancellare il riferimento quando l'oggetto muore.
Per sfruttare questo vantaggio, la classe deve avere una proprietà Events impostata nella [BaseType]
dichiarazione e anche la KeepUntilRef
variabile impostata sul nome del metodo richiamato quando l'oggetto ha completato il suo lavoro, come illustrato di seguito:
[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
[Export ("show")]
void Show (string message);
}
Ereditarietà dei protocolli
A partire da Xamarin.iOS v3.2, è supportato l'ereditarietà dai protocolli contrassegnati con la [Model]
proprietà . Ciò è utile in determinati modelli DI API, ad esempio in MapKit
cui il MKOverlay
protocollo eredita dal MKAnnotation
protocollo e viene adottato da una serie di classi che ereditano da NSObject
.
Storicamente era necessario copiare il protocollo in ogni implementazione, ma in questi casi ora è possibile avere la MKShape
classe eredita dal MKOverlay
protocollo e genererà automaticamente tutti i metodi necessari.