Bibliotecas de associação Objective-C
Ao trabalhar com o Xamarin.iOS ou o Xamarin.Mac, você pode encontrar casos em que deseja consumir uma biblioteca de terceiros Objective-C . Nessas situações, você pode usar Projetos de Associação do Xamarin para criar uma associação C# para as bibliotecas nativas Objective-C . O projeto usa as mesmas ferramentas que usamos para trazer as APIs do iOS e do Mac para o C#.
Este documento descreve como associar Objective-C APIs, se você estiver associando apenas APIs C, deverá usar o mecanismo .NET padrão para isso, a estrutura P/Invoke. Detalhes sobre como vincular estaticamente uma biblioteca C estão disponíveis na página Vinculando bibliotecas nativas.
Consulte nosso Guia de Referência de Tipos de Associação. Além disso, se você quiser saber mais sobre o que acontece nos bastidores, consulte nossa página Visão geral da vinculação .
As associações podem ser criadas para bibliotecas iOS e Mac. Esta página descreve como trabalhar em uma ligação do iOS, no entanto, as associações do Mac são muito semelhantes.
Código de exemplo para iOS
Você pode usar o projeto de exemplo de associação do iOS para experimentar associações.
Introdução
A maneira mais fácil de criar uma associação é criar um Projeto de Associação do Xamarin.iOS. Você pode fazer isso no Visual Studio para Mac selecionando o tipo de projeto, Biblioteca de Associações de Biblioteca >do iOS>:
O projeto gerado contém um pequeno modelo que você pode editar, ele contém dois arquivos: ApiDefinition.cs
e StructsAndEnums.cs
.
É ApiDefinition.cs
aqui que você definirá o contrato de API, este é o arquivo que descreve como a API subjacente Objective-C é projetada em C#. A sintaxe e o conteúdo desse arquivo são o principal tópico de discussão deste documento e o conteúdo dele é limitado a interfaces C# e declarações de delegado C#. O StructsAndEnums.cs
arquivo é o arquivo no qual você inserirá todas as definições exigidas pelas interfaces e delegados. Isso inclui valores de enumeração e estruturas que seu código pode usar.
Vinculando uma API
Para fazer uma associação abrangente, você desejará entender a definição da Objective-C API e se familiarizar com as diretrizes de design do .NET Framework.
Para vincular sua biblioteca, você normalmente começará com um arquivo de definição de API. Um arquivo de definição de API é apenas um arquivo de origem C# que contém interfaces C# que foram anotadas com um punhado de atributos que ajudam a conduzir a associação. Esse arquivo é o que define o que é o contrato entre C# e Objective-C é.
Por exemplo, este é um arquivo de API trivial para uma biblioteca:
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);
}
}
O exemplo acima define uma classe chamada Cocos2D.Camera
que deriva do NSObject
tipo base (esse tipo vem de Foundation.NSObject
) e que define uma propriedade estática (ZEye
), dois métodos que não recebem argumentos e um método que usa três argumentos.
Uma discussão aprofundada sobre o formato do arquivo de API e os atributos que você pode usar é abordada na seção Arquivo de definição de API abaixo.
Para produzir uma encadernação completa, você normalmente lidará com quatro componentes:
- O arquivo de definição de API (
ApiDefinition.cs
no modelo). - Opcional: quaisquer enumerações, tipos, structs exigidos pelo arquivo de definição de API (
StructsAndEnums.cs
no modelo). - Opcional: fontes extras que podem expandir a associação gerada ou fornecer uma API mais amigável para C# (qualquer arquivo C# que você adicionar ao projeto).
- A biblioteca nativa que você está vinculando.
Este gráfico mostra a relação entre os arquivos:
O arquivo de definição de API conterá apenas namespaces e definições de interface (com todos os membros que uma interface pode conter) e não deve conter classes, enumerações, delegados ou structs. O arquivo de definição de API é apenas o contrato que será usado para gerar a API.
Qualquer código extra que você precisar, como enumerações ou classes de suporte, deve ser hospedado em um arquivo separado, no exemplo acima, o "CameraMode" é um valor de enumeração que não existe no arquivo CS e deve ser hospedado em um arquivo separado, por exemplo StructsAndEnums.cs
:
public enum CameraMode {
FlyOver, Back, Follow
}
O APIDefinition.cs
arquivo é combinado com a StructsAndEnum
classe e é usado para gerar a ligação principal da biblioteca. Você pode usar a biblioteca resultante no estado em que se encontra, mas, normalmente, desejará ajustar a biblioteca resultante para adicionar alguns recursos do C# para o benefício de seus usuários. Alguns exemplos incluem a implementação de um ToString()
método, fornecer indexadores C#, adicionar conversões implícitas de e para alguns tipos nativos ou fornecer versões fortemente tipadas de alguns métodos. Essas melhorias são armazenadas em arquivos C# extras. Basta adicionar os arquivos C# ao seu projeto e eles serão incluídos neste processo de compilação.
Isso mostra como você implementaria o código em seu Extra.cs
arquivo. Observe que você usará classes parciais, pois elas aumentam as classes parciais geradas a partir da combinação da associação principal e StructsAndEnums.cs
principalApiDefinition.cs
:
public partial class Camera {
// Provide a ToString method
public override string ToString ()
{
return String.Format ("ZEye: {0}", ZEye);
}
}
A criação da biblioteca produzirá sua associação nativa.
Para concluir essa associação, você deve adicionar a biblioteca nativa ao projeto. Você pode fazer isso adicionando a biblioteca nativa ao seu projeto, arrastando e soltando a biblioteca nativa do Finder no projeto no gerenciador de soluções ou clicando com o botão direito do mouse no projeto e escolhendo Adicionar>Adicionar Arquivos para selecionar a biblioteca nativa. As bibliotecas nativas por convenção começam com a palavra "lib" e terminam com a extensão ".a". Quando você fizer isso, o Visual Studio para Mac adicionará dois arquivos: o arquivo .a e um arquivo C# preenchido automaticamente que contém informações sobre o que a biblioteca nativa contém:
O conteúdo do libMagicChord.linkwith.cs
arquivo contém informações sobre como essa biblioteca pode ser usada e instrui o IDE a empacotar esse binário no arquivo DLL resultante:
using System;
using ObjCRuntime;
[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]
Detalhes completos sobre como usar o [LinkWith]
estão documentados no Guia de Referência de Tipos de Associação.
Agora, ao criar o projeto, você acabará com um MagicChords.dll
arquivo que contém a ligação e a biblioteca nativa. Você pode distribuir esse projeto ou a DLL resultante para outros desenvolvedores para uso próprio.
Às vezes, você pode achar que precisa de alguns valores de enumeração, definições de delegado ou outros tipos. Não os coloque no arquivo de definições de API, pois isso é apenas um contrato
O arquivo de definição de API
O arquivo de definição de API consiste em várias interfaces. As interfaces na definição da API serão transformadas em uma declaração de classe e devem ser decoradas com o [BaseType]
atributo para especificar a classe base da classe.
Você pode estar se perguntando por que não usamos classes em vez de interfaces para a definição do contrato. Escolhemos interfaces porque isso nos permitiu escrever o contrato para um método sem ter que fornecer um corpo de método no arquivo de definição da API ou ter que fornecer um corpo que tivesse que lançar uma exceção ou retornar um valor significativo.
Mas como estamos usando a interface como um esqueleto para gerar uma classe, tivemos que recorrer à decoração de várias partes do contrato com atributos para conduzir a ligação.
Métodos de ligação
A ligação mais simples que você pode fazer é vincular um método. Basta declarar um método na interface com as convenções de nomenclatura C# e decorar o método com o método Atributo [Export]
. O [Export]
atributo é o que vincula seu nome C# ao Objective-C nome no runtime do Xamarin.iOS. O parâmetro do [Export]
é o nome do Objective-C seletor. Alguns exemplos:
// 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);
Os exemplos acima mostram como você pode associar métodos de instância. Para associar métodos estáticos, você deve usar o [Static]
atributo, assim:
// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();
Isso é necessário porque o contrato faz parte de uma interface e as interfaces não têm noção de declarações estáticas versus instância, portanto, é necessário mais uma vez recorrer a atributos. Se você quiser ocultar um método específico da associação, poderá decorar o método com o [Internal]
atributo.
O btouch-native
comando introduzirá verificações para que os parâmetros de referência não sejam nulos. Se você quiser permitir valores nulos para um parâmetro específico, use o método [NullAllowed]
no parâmetro, assim:
[Export ("setText:")]
string SetText ([NullAllowed] string text);
Ao exportar um tipo de referência, com a [Export]
palavra-chave, você também pode especificar a semântica de alocação. Isso é necessário para garantir que nenhum dado seja vazado.
Propriedades da associação
Assim como os métodos, Objective-C as propriedades são vinculadas usando o método [Export]
e mapear diretamente para propriedades C#. Assim como os métodos, as propriedades podem ser decoradas com o [Static]
e o [Internal]
Atributos.
Quando você usa o [Export]
atributo em uma propriedade nos bastidores, o btouch-native na verdade vincula dois métodos: o getter e o setter. O nome que você fornece para exportar é o nome base e o setter é calculado acrescentando a palavra "set", transformando a primeira letra do nome base em maiúscula e fazendo com que o seletor receba um argumento. Isso significa que [Export ("label")]
aplicado em uma propriedade realmente associa os métodos "label" e "setLabel:". Objective-C
Às vezes, as Objective-C propriedades não seguem o padrão descrito acima e o nome é substituído manualmente. Nesses casos, você pode controlar a maneira como a associação é gerada usando o método [Bind]
no getter ou setter, por exemplo:
[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }
Isso então vincula "isMenuVisible" e "setMenuVisible:". Opcionalmente, uma propriedade pode ser associada usando a seguinte sintaxe:
[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
[Export ("name")]
string Name();
[Export("setName:")]
void SetName(string name);
}
Onde o getter e o name
setter são explicitamente definidos como nas ligações e setName
acima.
Além do suporte para propriedades estáticas usando [Static]
, você pode decorar propriedades estáticas de thread com [IsThreadStatic]
, por exemplo:
[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }
Assim como os métodos permitem que alguns parâmetros sejam sinalizados com [NullAllowed]
, você pode aplicar [NullAllowed]
a uma propriedade para indicar que null é um valor válido para a propriedade, por exemplo:
[Export ("text"), NullAllowed]
string Text { get; set; }
O [NullAllowed]
parâmetro também pode ser especificado diretamente no setter:
[Export ("text")]
string Text { get; [NullAllowed] set; }
Advertências de controles personalizados de associação
As seguintes advertências devem ser consideradas ao configurar a associação para um controle personalizado:
- As propriedades de associação devem ser estáticas - Ao definir a associação de propriedades, o
[Static]
atributo deve ser usado. - Os nomes de propriedade devem corresponder exatamente – o nome usado para associar a propriedade deve corresponder exatamente ao nome da propriedade no controle personalizado.
- Os tipos de propriedade devem corresponder exatamente – o tipo de variável usado para associar a propriedade deve corresponder exatamente ao tipo da propriedade no controle personalizado.
- Pontos de interrupção e getter/setter - Os pontos de interrupção colocados nos métodos getter ou setter da propriedade nunca serão atingidos.
- Observar retornos de chamada - Você precisará usar retornos de chamada de observação para ser notificado sobre alterações nos valores de propriedade de controles personalizados.
A falha em observar qualquer uma das advertências listadas acima pode resultar na falha silenciosa da associação em tempo de execução.
Objective-C padrão e propriedades mutáveis
Objective-C Os frameworks usam um idioma em que algumas classes são imutáveis com uma subclasse mutável. Por exemplo NSString
é a versão imutável, enquanto NSMutableString
é a subclasse que permite a mutação.
Nessas classes, é comum ver a classe base imutável conter propriedades com um getter, mas nenhum setter. E para a versão mutável para introduzir o setter. Como isso não é realmente possível com C#, tivemos que mapear esse idioma em um idioma que funcionasse com C#.
A maneira como isso é mapeado para C# é adicionando o getter e o setter na classe base, mas sinalizando o setter com um Atributo [NotImplemented]
.
Em seguida, na subclasse mutável, você usa o comando [Override]
na propriedade para garantir que a propriedade esteja realmente substituindo o comportamento do pai.
Exemplo:
[BaseType (typeof (NSObject))]
interface MyTree {
string Name { get; [NotImplemented] set; }
}
[BaseType (typeof (MyTree))]
interface MyMutableTree {
[Override]
string Name { get; set; }
}
Construtores de associação
A btouch-native
ferramenta irá gerar automaticamente quatro construtores em sua classe, para uma determinada classe Foo
, ela gera:
Foo ()
: o construtor padrão (mapeia para Objective-Co construtor "init" da )Foo (NSCoder)
: o construtor usado durante a desserialização de arquivos NIB (mapeia para Objective-Co construtor "initWithCoder:" da ).Foo (IntPtr handle)
: o construtor para criação baseada em identificador, isso é invocado pelo runtime quando o runtime precisa expor um objeto gerenciado de um objeto não gerenciado.Foo (NSEmptyFlag)
: isso é usado por classes derivadas para evitar a inicialização dupla.
Para construtores que você define, eles precisam ser declarados usando a seguinte assinatura dentro da definição de Interface: eles devem retornar um IntPtr
valor e o nome do método deve ser Constructor. Por exemplo, para associar o initWithFrame:
construtor, isso é o que você usaria:
[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);
Protocolos de associação
Conforme descrito no documento de design da API, na seção que discute Modelos e Protocolos, o Xamarin.iOS mapeia os Objective-C protocolos em classes que foram sinalizadas com o Atributo [Model]
. Isso normalmente é usado ao implementar Objective-C classes delegadas.
A grande diferença entre uma classe associada regular e uma classe delegada é que a classe delegada pode ter um ou mais métodos opcionais.
Por exemplo, considere a UIKit
classe UIAccelerometerDelegate
, é assim que ela é associada no Xamarin.iOS:
[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
[Export ("accelerometer:didAccelerate:")]
void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}
Como este é um método opcional na definição, não UIAccelerometerDelegate
há mais nada a fazer. Mas se houver um método necessário no protocolo, você deve adicionar o método [Abstract]
ao método. Isso forçará o usuário da implementação a realmente fornecer um corpo para o método.
Em geral, os protocolos são usados em classes que respondem a mensagens. Isso normalmente é feito Objective-C atribuindo à propriedade "delegate" uma instância de um objeto que responde aos métodos no protocolo.
A convenção no Xamarin.iOS é dar suporte Objective-C ao estilo acoplado de forma flexível em que qualquer instância de um NSObject
pode ser atribuída ao delegado e também expor uma versão fortemente tipada dele. Por esse motivo, normalmente fornecemos uma Delegate
propriedade fortemente tipada e uma WeakDelegate
que é vagamente tipada. Geralmente vinculamos a versão de tipo livre com [Export]
, e usamos o [Wrap]
atributo para fornecer a versão de tipo forte.
Isso mostra como vinculamos a 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; }
}
Novidades no MonoTouch 7.0
A partir do MonoTouch 7.0, uma nova e aprimorada funcionalidade de vinculação de protocolo foi incorporada. Esse novo suporte simplifica o uso Objective-C de expressões idiomáticas para a adoção de um ou mais protocolos em uma determinada classe.
Para cada definição MyProtocol
de protocolo no Objective-C, agora há uma IMyProtocol
interface que lista todos os métodos necessários do protocolo, bem como uma classe de extensão que fornece todos os métodos opcionais. O acima, combinado com o novo suporte no editor do Xamarin Studio, permite que os desenvolvedores implementem métodos de protocolo sem precisar usar as subclasses separadas das classes de modelo abstratas anteriores.
Qualquer definição que contenha o atributo gerará, na verdade, [Protocol]
três classes de suporte que melhoram muito a maneira como você consome protocolos:
// 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);
}
}
A implementação da classe fornece uma classe abstrata completa da qual você pode substituir métodos individuais e obter segurança de tipo completa. Mas, devido ao C# não dar suporte a várias heranças, há cenários em que talvez você precise ter uma classe base diferente, mas ainda deseja implementar uma interface, que é onde o
A definição de interface gerada entra. É uma interface que possui todos os métodos necessários do protocolo. Isso permite que os desenvolvedores que desejam implementar seu protocolo simplesmente implementem a interface. O runtime registrará automaticamente o tipo como adotando o protocolo.
Observe que a interface lista apenas os métodos necessários e expõe os métodos opcionais. Isso significa que as classes que adotam o protocolo obterão verificação completa de tipo para os métodos necessários, mas terão que recorrer à tipagem fraca (usando [Export]
atributos manualmente e combinando a assinatura) para os métodos de protocolo opcionais.
Para facilitar o consumo de uma API que usa protocolos, a ferramenta de associação também produzirá uma classe de método de extensões que expõe todos os métodos opcionais. Isso significa que, enquanto você estiver consumindo uma API, poderá tratar os protocolos como tendo todos os métodos.
Se você quiser usar as definições de protocolo em sua API, precisará escrever interfaces vazias de esqueleto em sua definição de API. Se você quiser usar o MyProtocol em uma API, precisará fazer o seguinte:
[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 ();
}
O acima é necessário porque no momento da ligação o IMyProtocol
não existiria, é por isso que você precisa fornecer uma interface vazia.
Adotando interfaces geradas por protocolo
Sempre que você implementar uma das interfaces geradas para os protocolos, assim:
class MyDelegate : NSObject, IUITableViewDelegate {
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
A implementação dos métodos de interface necessários é exportada com o nome adequado, portanto, é equivalente a isso:
class MyDelegate : NSObject, IUITableViewDelegate {
[Export ("getRowHeight:")]
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
Isso funcionará para todos os membros de protocolo necessários, mas há um caso especial com seletores opcionais a serem considerados. Os membros opcionais do protocolo são tratados de forma idêntica ao usar a classe base:
public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
mas ao usar a interface do protocolo, é necessário adicionar o [Export]. O IDE irá adicioná-lo por meio do preenchimento automático quando você adicioná-lo começando com a substituição.
public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
Há uma pequena diferença de comportamento entre os dois em tempo de execução.
- Os usuários da classe base (NSUrlSessionDownloadDelegate no exemplo) fornecem todos os seletores obrigatórios e opcionais, retornando valores padrão razoáveis.
- Os usuários da interface (INSUrlSessionDownloadDelegate no exemplo) respondem apenas aos seletores exatos fornecidos.
Algumas classes raras podem se comportar de maneira diferente aqui. Em quase todos os casos, no entanto, é seguro usar qualquer um.
Extensões de classe de associação
Nele Objective-C é possível estender classes com novos métodos, semelhantes em espírito aos métodos de extensão do C#. Quando um desses métodos estiver presente, você poderá usar o comando [BaseType]
para sinalizar o método como sendo o receptor da Objective-C mensagem.
Por exemplo, no Xamarin.iOS, associamos os métodos de extensão definidos em NSString
quando UIKit
é importado NSStringDrawingExtensions
como métodos no , assim:
[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
[Export ("drawAtPoint:withFont:")]
CGSize DrawString (CGPoint point, UIFont font);
}
Listas de argumentos de associação Objective-C
Objective-C apóia argumentos variádicos. Por exemplo:
- (void) appendWorkers:(XWorker *) firstWorker, ...
NS_REQUIRES_NIL_TERMINATION ;
Para invocar esse método do C#, você desejará criar uma assinatura como esta:
[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)
Isso declara o método como interno, ocultando a API acima dos usuários, mas expondo-a à biblioteca. Então você pode escrever um método como este:
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);
}
Campos de vinculação
Às vezes, você desejará acessar campos públicos que foram declarados em uma biblioteca.
Normalmente, esses campos contêm valores de strings ou inteiros que devem ser referenciados. Eles são comumente usados como string que representa uma notificação específica e como chaves em dicionários.
Para vincular um campo, adicione uma propriedade ao arquivo de definição de interface e decore a propriedade com o [Field]
atributo. Esse atributo usa um parâmetro: o nome C do símbolo a ser pesquisado. Por exemplo:
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
Se você quiser encapsular vários campos em uma classe estática que não deriva de NSObject
, você pode usar o [Static]
na classe, assim:
[Static]
interface LonelyClass {
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
}
O acima gerará um LonelyClass
que não deriva e NSObject
conterá uma ligação ao NSSomeEventNotification
NSString
exposto como um NSString
.
O [Field]
atributo pode ser aplicado aos seguintes tipos de dados:
NSString
referências (somente propriedades somente leitura)NSArray
referências (somente propriedades somente leitura)- Ints de 32 bits (
System.Int32
) - Ints de 64 bits (
System.Int64
) - Floats de 32 bits (
System.Single
) - Floats de 64 bits (
System.Double
) System.Drawing.SizeF
CGSize
Além do nome do campo nativo, você pode especificar o nome da biblioteca onde o campo está localizado, passando o nome da biblioteca:
[Static]
interface LonelyClass {
[Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
NSString SomeSharedLibrarySymbol { get; }
}
Se você estiver vinculando estaticamente, não há biblioteca para vincular, então você precisa usar o __Internal
nome:
[Static]
interface LonelyClass {
[Field ("MyFieldFromALibrary", "__Internal")]
NSString MyFieldFromALibrary { get; }
}
Enumerações de associação
Você pode adicionar enum
diretamente em seus arquivos de associação para facilitar o uso deles dentro das definições de API - sem usar um arquivo de origem diferente (que precisa ser compilado nas associações e no projeto final).
Exemplo:
[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}
interface MyType {
[Export ("initWithEnum:")]
IntPtr Constructor (MyEnum value);
}
Também é possível criar suas próprias enumerações para substituir NSString
constantes. Nesse caso, o gerador criará automaticamente os métodos para converter valores de enumerações e constantes NSString para você.
Exemplo:
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);
}
No exemplo acima, você pode decidir decorar void Perform (NSString mode);
com um [Internal]
atributo. Isso ocultará a API baseada em constante de seus consumidores de associação.
No entanto, isso limitaria a subclasse do tipo, pois a alternativa de API mais agradável usa um [Wrap]
atributo. Esses métodos gerados não virtual
são , ou seja, você não poderá substituí-los - o que pode, ou não, ser uma boa escolha.
Uma alternativa é marcar a definição original, NSString
baseada em [Protected]
. Isso permitirá que a subclasse funcione, quando necessário, e a versão encapsulada ainda funcionará e chamará o método substituído.
Vinculação NSValue
, NSNumber
, e NSString
para um tipo melhor
O [BindAs]
atributo permite a associação NSNumber
de e NSValue
NSString
(enumerações) em tipos C# mais precisos. O atributo pode ser usado para criar uma API .NET melhor e mais precisa sobre a API nativa.
Você pode decorar métodos (no valor retornado), parâmetros e propriedades com [BindAs]
.
A única restrição é que seu membro não deve estar dentro de um [Protocol]
ou [Model]
interface.
Por exemplo:
[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);
Produziria:
[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }
Internamente, faremos as bool?
conversões ->NSNumber
e CGRect
<->NSValue
.<
[BindAs]
também suporta matrizes de NSNumber
NSValue
e NSString
(enumerações).
Por exemplo:
[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }
Produziria:
[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }
CAScroll
for uma NSString
enumeração com suporte, buscaremos o valor correto NSString
e lidaremos com a conversão de tipo.
Consulte a documentação para ver os [BindAs]
tipos de conversão compatíveis.
Notificações de vinculação
Notificações são mensagens postadas no NSNotificationCenter.DefaultCenter
e usadas como um mecanismo para transmitir mensagens de uma parte do aplicativo para outra. Os desenvolvedores assinam notificações normalmente usando o método AddObserver do NSNotificationCenter. Quando um aplicativo posta uma mensagem no centro de notificações, ele normalmente contém uma carga armazenada no dicionário NSNotification.UserInfo . Este dicionário é fracamente tipado e obter informações dele é propenso a erros, pois os usuários normalmente precisam ler na documentação quais chaves estão disponíveis no dicionário e os tipos de valores que podem ser armazenados no dicionário. A presença de chaves às vezes também é usada como booleana.
O gerador de associação Xamarin.iOS fornece suporte para desenvolvedores associarem notificações. Para fazer isso, defina o [Notification]
em uma propriedade que também foi marcada com um atributo [Field]
propriedade (pode ser pública ou privada).
Esse atributo pode ser usado sem argumentos para notificações que não carregam carga ou você pode especificar um System.Type
que faz referência a outra interface na definição da API, normalmente com o nome terminando com "EventArgs". O gerador transformará a interface em uma classe que subclasse EventArgs
e incluirá todas as propriedades listadas lá. O [Export]
atributo deve ser usado na classe EventArgs para listar o nome da chave usada para pesquisar o Objective-C dicionário para buscar o valor.
Por exemplo:
interface MyClass {
[Notification]
[Field ("MyClassDidStartNotification")]
NSString DidStartNotification { get; }
}
O código acima gerará uma classe MyClass.Notifications
aninhada com os seguintes métodos:
public class MyClass {
[..]
public Notifications {
public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
}
}
Os usuários do seu código podem assinar facilmente as notificações postadas no NSDefaultCenter usando um código como este:
var token = MyClass.Notifications.ObserverDidStart ((notification) => {
Console.WriteLine ("Observed the 'DidStart' event!");
});
O valor retornado de ObserveDidStart
pode ser usado para parar facilmente de receber notificações, assim:
token.Dispose ();
Ou você pode chamar NSNotification.DefaultCenter.RemoveObserver e passar o token. Se sua notificação contiver parâmetros, você deverá especificar uma interface auxiliar EventArgs
, como esta:
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; }
}
O acima gerará uma MyScreenChangedEventArgs
classe com as ScreenX
propriedades and ScreenY
que buscará os dados do dicionário NSNotification.UserInfo usando as chaves "ScreenXKey" e "ScreenYKey", respectivamente, e aplicará as conversões adequadas. O [ProbePresence]
atributo é usado para o gerador investigar se a chave está definida no UserInfo
, em vez de tentar extrair o valor. Isso é usado para casos em que a presença da chave é o valor (normalmente para valores booleanos).
Isso permite que você escreva um código como este:
var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});
Categorias de associação
As categorias são um Objective-C mecanismo usado para estender o conjunto de métodos e propriedades disponíveis em uma classe. Na prática, eles são usados para estender a funcionalidade de uma classe base (por exemplo NSObject
) quando uma estrutura específica está vinculada (por exemplo UIKit
), disponibilizando seus métodos, mas somente se a nova estrutura estiver vinculada. Em alguns outros casos, eles são usados para organizar recursos em uma classe por funcionalidade. Eles são semelhantes em espírito aos métodos de extensão C#. É assim que uma categoria ficaria em Objective-C:
@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end
O exemplo acima, se encontrado em uma biblioteca, estenderia instâncias de UIView
com o método makeBackgroundRed
.
Para associá-los, você pode usar o [Category]
atributo em uma definição de interface. Ao usar o [Category]
atributo, o significado do atributo [BaseType]
O atributo deixa de ser usado para especificar a classe base a ser estendida, para ser o tipo a ser estendido.
O seguinte mostra como as UIView
extensões são associadas e transformadas em métodos de extensão C#:
[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
[Export ("makeBackgroundRed")]
void MakeBackgroundRed ();
}
O acima criará uma MyUIViewExtension
classe que contém o MakeBackgroundRed
método de extensão. Isso significa que agora você pode chamar "MakeBackgroundRed" em qualquer UIView
subclasse, oferecendo a mesma funcionalidade que você obteria no Objective-C. Em alguns outros casos, as categorias são usadas não para estender uma classe de sistema, mas para organizar a funcionalidade, puramente para fins de decoração. Dessa forma:
@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end
@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end
Embora você possa usar o [Category]
também para esse estilo de decoração de declarações, você também pode adicioná-las todas à definição de classe. Ambos alcançariam o mesmo:
[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);
}
Nesses casos, é apenas mais curto mesclar as categorias:
[BaseType (typeof (NSObject))]
interface SocialNetworking {
[Export ("postToTwitter:")]
void PostToTwitter (Message message);
[Export ("postToFacebook:andPicture:")]
void PostToFacebook (Message message, UIImage picture);
}
Blocos de ligação
Os blocos são uma nova construção introduzida pela Apple para trazer o equivalente funcional dos métodos anônimos do C# para Objective-Co . Por exemplo, a NSSet
classe agora expõe este método:
- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block
A descrição acima declara um método chamado enumerateObjectsUsingBlock:
que usa um argumento chamado block
. Esse bloco é semelhante a um método anônimo C#, pois tem suporte para capturar o ambiente atual (o ponteiro "this", acesso a variáveis e parâmetros locais). O método NSSet
acima invoca o bloco com dois parâmetros: um NSObject
(a id obj
parte) e um ponteiro para uma parte booleana (a BOOL *stop
).
Para associar esse tipo de API ao btouch, você precisa primeiro declarar a assinatura do tipo de bloco como um delegado C# e, em seguida, referenciá-la de um ponto de entrada da API, desta forma:
// 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)
E agora seu código pode chamar sua função de 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);
});
Você também pode usar lambdas, se preferir:
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);
});
Métodos assíncronos
O gerador de associação pode transformar uma determinada classe de métodos em métodos amigáveis assíncronos (métodos que retornam uma Tarefa ou Tarefa<T>).
Você pode usar o [Async]
em métodos que retornam void e cujo último argumento é um retorno de chamada. Quando você aplica isso a um método, o gerador de associação gerará uma versão desse método com o sufixo Async
. Se o retorno de chamada não usar parâmetros, o valor retornado será um Task
, se o retorno de chamada usar um parâmetro, o resultado será um Task<T>
. Se o retorno de chamada usar vários parâmetros, você deverá definir o ResultType
ou ResultTypeName
para especificar o nome desejado do tipo gerado que conterá todas as propriedades.
Exemplo:
[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);
O código acima gerará o método LoadFile, bem como:
[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);
Exibindo tipos fortes para parâmetros NSDictionary fracos
Em muitos lugares na API, os Objective-C parâmetros são passados como APIs de tipo NSDictionary
fraco com chaves e valores específicos, mas eles são propensos a erros (você pode passar chaves inválidas e não receber avisos; você pode passar valores inválidos e não receber avisos) e frustrantes de usar, pois exigem várias viagens à documentação para pesquisar os possíveis nomes e valores de chave.
A solução é fornecer uma versão fortemente tipada que forneça a versão fortemente tipada da API e mapeie nos bastidores as várias chaves e valores subjacentes.
Por exemplo, se a Objective-C API aceitou um NSDictionary
e está documentado como usando a chave XyzVolumeKey
que recebe um NSNumber
com um valor de volume de 0,0 a 1,0 e um XyzCaptionKey
que usa uma string, você gostaria que seus usuários tivessem uma boa API com esta aparência:
public class XyzOptions {
public nfloat? Volume { get; set; }
public string Caption { get; set; }
}
A Volume
propriedade é definida como float anulável, pois a convenção em Objective-C não exige que esses dicionários tenham o valor, portanto, há cenários em que o valor pode não ser definido.
Para fazer isso, você precisa fazer algumas coisas:
- Crie uma classe fortemente tipada, que subclasse DictionaryContainer e fornece os vários getters e setters para cada propriedade.
- Declare sobrecargas para os métodos que tomam
NSDictionary
para usar a nova versão fortemente tipada.
Você pode criar a classe fortemente tipada manualmente ou usar o gerador para fazer o trabalho para você. Primeiro, exploramos como fazer isso manualmente para que você entenda o que está acontecendo e, em seguida, a abordagem automática.
Você precisa criar um arquivo de suporte para isso, ele não entra na API do contrato. Isto é o que você teria que escrever para criar sua 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
}
Em seguida, você deve fornecer um método wrapper que exiba a API de alto nível, sobre a API de baixo nível.
[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 sua API não precisar ser substituída, você poderá ocultar com segurança a API baseada em NSDictionary usando o comando Atributo [Internal]
.
Como você pode ver, usamos o método [Wrap]
atributo para exibir um novo ponto de entrada da API e o exibimos usando nossa classe fortemente tipada XyzOptions
. O método wrapper também permite que null seja passado.
Agora, uma coisa que não mencionamos é de onde vieram os XyzOptionsKeys
valores. Normalmente, você agruparia as chaves que uma API apresenta em uma classe estática como XyzOptionsKeys
, assim:
[Static]
class XyzOptionKeys {
[Field ("kXyzVolumeKey")]
NSString VolumeKey { get; }
[Field ("kXyzCaptionKey")]
NSString CaptionKey { get; }
}
Vejamos o suporte automático para a criação desses dicionários fortemente tipados. Isso evita muitos clichês e você pode definir o dicionário diretamente em seu contrato de API, em vez de usar um arquivo externo.
Para criar um dicionário fortemente tipado, introduza uma interface em sua API e decore-a com o atributo StrongDictionary . Isso informa ao gerador que ele deve criar uma classe com o mesmo nome da interface que derivará e DictionaryContainer
fornecerá acessadores tipados fortes para ela.
O [StrongDictionary]
atributo usa um parâmetro, que é o nome da classe estática que contém suas chaves de dicionário. Em seguida, cada propriedade da interface se tornará um acessador fortemente tipado. Por padrão, o código usará o nome da propriedade com o sufixo "Key" na classe estática para criar o acessador.
Isso significa que a criação de seu acessador fortemente tipado não requer mais um arquivo externo, nem a necessidade de criar manualmente getters e setters para cada propriedade, nem a necessidade de pesquisar as chaves manualmente.
É assim que toda a sua ligação ficaria:
[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);
}
Caso você precise fazer referência em seus XyzOption
membros a um campo diferente (que não seja o nome da propriedade com o sufixo Key
), você pode decorar a propriedade com um [Export]
pelo nome que você deseja usar.
Mapeamentos de Tipo
Esta seção aborda como Objective-C os tipos são mapeados para tipos C#.
Tipos simples
A tabela a seguir mostra como você deve mapear tipos do Objective-C mundo e CocoaTouch para o mundo Xamarin.iOS:
Objective-C Nome do tipo | Tipo de API unificada do Xamarin.iOS |
---|---|
BOOL , GLboolean |
bool |
NSInteger |
nint |
NSUInteger |
nuint |
CFTimeInterval / NSTimeInterval |
double |
NSString (mais sobre a ligação NSString) |
string |
char * |
string (veja também: [PlainString] ) |
CGRect |
CGRect |
CGPoint |
CGPoint |
CGSize |
CGSize |
CGFloat , GLfloat |
nfloat |
Tipos CoreFoundation (CF* ) |
CoreFoundation.CF* |
GLint |
nint |
GLfloat |
nfloat |
Tipos de fundação (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 |
matrizes
O runtime do Xamarin.iOS cuida automaticamente da conversão de matrizes C# e NSArrays
da conversão de volta, por exemplo, o método imaginário Objective-C que retorna um NSArray
de UIViews
:
// Get the peer views - untyped
- (NSArray *)getPeerViews ();
// Set the views for this container
- (void) setViews:(NSArray *) views
Está encadernado assim:
[Export ("getPeerViews")]
UIView [] GetPeerViews ();
[Export ("setViews:")]
void SetViews (UIView [] views);
A ideia é usar uma matriz C# fortemente tipada, pois isso permitirá que o IDE forneça a conclusão de código adequada com o tipo real sem forçar o usuário a adivinhar ou consultar a documentação para descobrir o tipo real dos objetos contidos na matriz.
Nos casos em que você não pode rastrear o tipo mais derivado real contido na matriz, você pode usar NSObject []
como o valor retornado.
Seletores
Os seletores aparecem na Objective-C API como o tipo SEL
especial . Ao associar um seletor, você mapearia o tipo para ObjCRuntime.Selector
. Normalmente, os seletores são expostos em uma API com um objeto, o objeto de destino e um seletor a ser invocado no objeto de destino. Fornecer ambos corresponde basicamente ao delegado C#: algo que encapsula o método a ser invocado e o objeto no qual invocar o método.
Esta é a aparência da associação:
interface Button {
[Export ("setTarget:selector:")]
void SetTarget (NSObject target, Selector sel);
}
E é assim que o método normalmente seria usado em um aplicativo:
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
b.SetTarget (this, new Selector ("print"));
}
[Export ("print")]
void ThePrintMethod ()
{
// This does the printing
}
}
Para tornar a associação mais agradável para desenvolvedores de C#, você normalmente fornecerá um método que usa um NSAction
parâmetro, que permite que delegados e lambdas de C# sejam usados em vez do Target+Selector
. Para fazer isso, você normalmente ocultaria o SetTarget
método sinalizando-o com um [Internal]
e, em seguida, você exporia um novo método auxiliar, como este:
// 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);
}
}
Então, agora seu código de usuário pode ser escrito assim:
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
// First Style
b.SetTarget (ThePrintMethod);
// Lambda style
b.SetTarget (() => { /* print here */ });
}
void ThePrintMethod ()
{
// This does the printing
}
}
Cadeias de caracteres
Ao associar um método que usa um NSString
, você pode substituí-lo por um tipo de cadeia de caracteres C#, tanto em tipos de retorno quanto em parâmetros.
O único caso em que você pode querer usar um NSString
diretamente é quando a cadeia de caracteres é usada como um token. Para obter mais informações sobre cadeias de caracteres e NSString
, leia o documento Design da API no NSString .
Em alguns casos raros, uma API pode expor uma string semelhante a C (char *
) em vez de uma Objective-C string (NSString *
). Nesses casos, você pode anotar o parâmetro com o Atributo [PlainString]
.
parâmetros out/ref
Algumas APIs retornam valores em seus parâmetros ou passam parâmetros por referência.
Normalmente, a assinatura tem esta aparência:
- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref
O primeiro exemplo mostra um idioma comum Objective-C para retornar códigos de erro, um ponteiro para um NSError
ponteiro é passado e, ao retornar, o valor é definido. O segundo método mostra como um Objective-C método pode pegar um objeto e modificar seu conteúdo. Isso seria uma passagem por referência, em vez de um valor de saída puro.
Sua associação ficaria assim:
[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);
Atributos de gerenciamento de memória
Quando você usa o [Export]
atributo e está passando dados que serão retidos pelo método chamado, você pode especificar a semântica do argumento passando-a como um segundo parâmetro, por exemplo:
[Export ("method", ArgumentSemantic.Retain)]
O acima sinalizaria o valor como tendo a semântica "Reter". A semântica disponível é:
- Atribuir
- Copiar
- Manter
Diretrizes de estilo
Usando [Interno]
Você pode usar o [Internal]
para ocultar um método da API pública. Talvez você queira fazer isso nos casos em que a API exposta é de nível muito baixo e você deseja fornecer uma implementação de alto nível em um arquivo separado com base nesse método.
Você também pode usar isso quando se deparar com limitações no gerador de associação, por exemplo, alguns cenários avançados podem expor tipos que não estão associados e você deseja vincular à sua própria maneira e deseja encapsular esses tipos à sua maneira.
Manipuladores de eventos e retornos de chamada
Objective-C As classes normalmente transmitem notificações ou solicitam informações enviando uma mensagem em uma classe delegada (Objective-C delegate).
Esse modelo, embora totalmente suportado e apresentado pelo Xamarin.iOS, às vezes pode ser complicado. O Xamarin.iOS expõe o padrão de evento C# e um sistema de retorno de chamada de método na classe que pode ser usado nessas situações. Isso permite que um código como este seja executado:
button.Clicked += delegate {
Console.WriteLine ("I was clicked");
};
O gerador de associação é capaz de reduzir a quantidade de digitação necessária para mapear o Objective-C padrão no padrão C#.
A partir do Xamarin.iOS 1.4, também será possível instruir o gerador a produzir associações para delegados específicos Objective-C e expor o delegado como eventos e propriedades C# no tipo de host.
Existem duas classes envolvidas nesse processo, a classe host que will é aquela que atualmente emite eventos e os envia para a Delegate
classe or WeakDelegate
e a classe delegada real.
Considerando a seguinte configuração:
[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);
}
Para encerrar a aula, você deve:
- Em sua classe host, adicione ao seu
[BaseType]
o tipo que está atuando como seu delegado e o nome C# que você expôs. Em nosso exemplo acima, esses sãotypeof (MyClassDelegate)
eWeakDelegate
respectivamente. - Em sua classe delegada, em cada método que tem mais de dois parâmetros, você precisa especificar o tipo que deseja usar para a classe EventArgs gerada automaticamente.
O gerador de vinculação não se limita a encapsular apenas um único destino de evento, é possível que algumas Objective-C classes emitam mensagens para mais de um delegado, portanto, você terá que fornecer matrizes para dar suporte a essa configuração. A maioria das configurações não precisará dele, mas o gerador está pronto para suportar esses casos.
O código resultante será:
[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);
}
O EventArgs
é usado para especificar o nome da EventArgs
classe a ser gerada. Você deve usar um por assinatura (neste exemplo, o EventArgs
conterá uma With
propriedade do tipo nint).
Com as definições acima, o gerador produzirá o seguinte evento no MyClass gerado:
public MyClassLoadedEventArgs : EventArgs {
public MyClassLoadedEventArgs (nint bytes);
public nint Bytes { get; set; }
}
public event EventHandler<MyClassLoadedEventArgs> Loaded {
add; remove;
}
Então agora você pode usar o código assim:
MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};
Os retornos de chamada são como invocações de eventos, a diferença é que, em vez de ter vários assinantes em potencial (por exemplo, vários métodos podem se conectar a um Clicked
evento ou evento DownloadFinished
), os retornos de chamada podem ter apenas um único assinante.
O processo é idêntico, a única diferença é que, em vez de expor o nome da classe que será gerada, o EventArgs na verdade é usado para nomear o nome do EventArgs
delegado C# resultante.
Se o método na classe delegada retornar um valor, o gerador de associação mapeará isso em um método delegado na classe pai em vez de um evento. Nesses casos, você precisa fornecer o valor padrão que deve ser retornado pelo método se o usuário não se conectar ao delegado. Você faz isso usando o [DefaultValue]
ou [DefaultValueFromArgument]
atributos.
[DefaultValue]
codificará um valor retornado, enquanto [DefaultValueFromArgument]
é usado para especificar qual argumento de entrada será retornado.
Enumerações e tipos base
Você também pode fazer referência a enumerações ou tipos base que não são suportados diretamente pelo sistema de definição de interface btouch. Para fazer isso, coloque suas enumerações e tipos de núcleo em um arquivo separado e inclua-o como parte de um dos arquivos extras que você fornece ao btouch.
Vinculando as dependências
Se você estiver associando APIs que não fazem parte do seu aplicativo, precisará garantir que o executável esteja vinculado a essas bibliotecas.
Você precisa informar ao Xamarin.iOS como vincular suas bibliotecas, isso pode ser feito alterando sua configuração de build para invocar o mtouch
comando com alguns argumentos de build extras que especificam como vincular com as novas bibliotecas usando a opção "-gcc_flags", seguida por uma cadeia de caracteres entre aspas que contém todas as bibliotecas extras necessárias para seu programa, Assim:
-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"
O exemplo acima vinculará libMyLibrary.a
, libSystemLibrary.dylib
e a biblioteca de CFNetwork
estrutura ao executável final.
Ou você pode aproveitar o nível [LinkWithAttribute]
de assembly , que pode ser incorporado em seus arquivos de contrato (como AssemblyInfo.cs
).
Ao usar o [LinkWithAttribute]
, você precisará ter sua biblioteca nativa disponível no momento em que fizer a associação, pois isso incorporará a biblioteca nativa ao seu aplicativo. Por exemplo:
// 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)]
Você pode estar se perguntando, por que você precisa -force_load
de comando, e o motivo é que o sinalizador -ObjC, embora compile o código, ele não preserva os metadados necessários para dar suporte a categorias (a eliminação de código morto do vinculador/compilador o remove) que você precisa em runtime para Xamarin.iOS.
Referências assistidas
Alguns objetos transitórios, como folhas de ação e caixas de alerta, são complicados de acompanhar para os desenvolvedores e o gerador de associação pode ajudar um pouco aqui.
Por exemplo, se você tivesse uma classe que mostrasse uma mensagem e gerasse um Done
evento, a maneira tradicional de lidar com isso seria:
class Demo {
MessageBox box;
void ShowError (string msg)
{
box = new MessageBox (msg);
box.Done += { box = null; ... };
}
}
No cenário acima, o desenvolvedor precisa manter a referência ao objeto e vazar ou limpar ativamente a referência para a caixa por conta própria. Ao vincular o código, o gerador suporta manter o controle da referência para você e limpá-la quando um método especial é invocado, o código acima se tornaria:
class Demo {
void ShowError (string msg)
{
var box = new MessageBox (msg);
box.Done += { ... };
}
}
Observe como não é mais necessário manter a variável em uma instância, que ela funciona com uma variável local e que não é necessário limpar a referência quando o objeto morre.
Para aproveitar isso, sua classe deve ter uma propriedade Events definida na [BaseType]
declaração e também a KeepUntilRef
variável definida como o nome do método que é invocado quando o objeto conclui seu trabalho, assim:
[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
[Export ("show")]
void Show (string message);
}
Herdando protocolos
A partir do Xamarin.iOS v3.2, damos suporte à herança de protocolos que foram marcados com a [Model]
propriedade. Isso é útil em determinados padrões de API, como em MapKit
que o MKOverlay
protocolo herda do MKAnnotation
protocolo e é adotado por várias classes que herdam do NSObject
.
Historicamente, exigimos copiar o protocolo para cada implementação, mas nesses casos agora podemos fazer com que a MKShape
classe herde do MKOverlay
protocolo e ela gerará todos os métodos necessários automaticamente.