Share via


Bibliotecas de vinculaçã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 o Xamarin Binding Projects para criar uma vinculação C# às 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 vincular Objective-C APIs, se você estiver vinculando apenas APIs C, você deve 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 Vinculação complementar. Além disso, se você quiser saber mais sobre o que acontece sob o capô, consulte nossa página Visão geral da vinculação.

As ligações podem ser criadas para bibliotecas iOS e Mac. Esta página descreve como trabalhar em uma associação do iOS, no entanto, as vinculações do Mac são muito semelhantes.

Código de exemplo para iOS

Você pode usar o projeto iOS Binding Sample para experimentar associações.

Introdução

A maneira mais fácil de criar uma associação é criar um projeto de vinculação Xamarin.iOS. Você pode fazer isso no Visual Studio para Mac selecionando o tipo de projeto, iOS > Library > Bindings Library:

Faça isso no Visual Studio para Mac selecionando o tipo de projeto, Biblioteca de Ligações da 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.

O ApiDefinition.cs é onde 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 delegados C#. O StructsAndEnums.cs arquivo é o arquivo onde você inserirá quaisquer definições que são exigidas pelas interfaces e representantes. Isso inclui valores de enumeração e estruturas que seu código pode usar.

Vinculando uma API

Para fazer uma associação abrangente, convém entender a definição de Objective-C API e familiarizar-se 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 é meramente 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 usam argumentos e um método que usa três argumentos.

Uma discussão detalhada 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 associação completa, você normalmente lidará com quatro componentes:

  • O arquivo de definição de API (ApiDefinition.cs no modelo).
  • Opcional: quaisquer enums, 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 ao C# (quaisquer arquivos C# adicionados ao projeto).
  • A biblioteca nativa que você está vinculando.

Este gráfico mostra a relação entre os arquivos:

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 quaisquer membros que uma interface possa conter) e não deve conter classes, enumerações, delegados ou structs. O arquivo de definição de API é meramente o contrato que será usado para gerar a API.

Qualquer código extra que você precisa 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 são usados para gerar a associação principal da biblioteca. Você pode usar a biblioteca resultante como está, mas, normalmente, convém ajustar a biblioteca resultante para adicionar alguns recursos C# para o benefício de seus usuários. Alguns exemplos incluem implementar 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 à medida que elas aumentam as classes parciais geradas a partir da combinação da ApiDefinition.cs associação e da StructsAndEnums.cs ligação principal:

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 vinculaçã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 para o projeto no gerenciador de soluções ou clicando com o botão direito do mouse no projeto e escolhendo 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:

As bibliotecas nativas por convenção começam com a palavra lib e terminam com a extensão .a

O conteúdo do libMagicChord.linkwith.cs arquivo tem informações sobre como essa biblioteca pode ser usada e instrui seu 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] são documentados no Guia de Referência de Tipos de Vinculação.

Agora, quando você compilar o projeto, você acabará com um MagicChords.dll arquivo que contém a associação e a biblioteca nativa. Você pode distribuir este projeto ou a DLL resultante para outros desenvolvedores para seu próprio uso.

Às vezes, você pode achar que precisa de alguns valores de enumeração, definições delegadas ou outros tipos. Não os coloque no arquivo de definições de API, pois isso é meramente 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 de API serão transformadas em uma declaração de classe e devem ser decoradas com o [BaseType] atributo para especificar a classe base para a classe.

Você deve 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 de 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 impulsionar a ligação.

Métodos de vinculação

A vinculaçã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 Atributo [Export]. O [Export] atributo é o que vincula seu nome C# com o Objective-C nome no tempo de execução do Xamarin.iOS. O parâmetro do [Export] atributo é o Objective-C nome do 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 vincular métodos de instância. Para vincular métodos estáticos, você deve usar o [Static] atributo , da seguinte maneira:

// 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 vs instância, então é necessário mais uma vez recorrer a atributos. Se você quiser ocultar um método específico da associação, você pode 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 [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 [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 sob as capas, 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 precedendo a palavra "set", transformando a primeira letra do basename em maiúsculas e fazendo com que o seletor tome um argumento. Isso significa que [Export ("label")] aplicado em uma propriedade realmente vincula 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 [Bind] no getter ou setter, por exemplo:

[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }

Isso então liga "isMenuVisible" e "setMenuVisible:". Opcionalmente, uma propriedade pode ser vinculada 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 setter são explicitamente definidos como nas name ligações acima setName .

Além do suporte para propriedades estáticas usando [Static]o , 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] para 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 vinculação

As seguintes advertências devem ser consideradas ao configurar a associação para um controle personalizado:

  1. As propriedades de vinculação devem ser estáticas - Ao definir a vinculação de propriedades, o [Static] atributo deve ser usado.
  2. Os nomes de propriedade devem corresponder exatamente - O nome usado para vincular a propriedade deve corresponder exatamente ao nome da propriedade no controle personalizado.
  3. Os tipos de propriedade devem corresponder exatamente - O tipo de variável usado para vincular a propriedade deve corresponder exatamente ao tipo da propriedade no controle personalizado.
  4. Pontos de interrupção e o getter/setter - Os pontos de interrupção colocados nos métodos getter ou setter da propriedade nunca serão atingidos.
  5. Observe 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 não observância de qualquer uma das advertências listadas acima pode resultar na falha silenciosa da vinculação em tempo de execução.

Objective-C padrão e propriedades mutáveis

Objective-C frameworks usam um idioma onde 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 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, use o [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 vinculaçã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")
  • Foo (NSCoder): o construtor usado durante a desserialização de arquivos NIB (mapeia para Objective-Co construtor "initWithCoder:" de ').
  • Foo (IntPtr handle): o construtor para criação baseada em manipulação, isso é chamado pelo tempo de execução quando o tempo de execução precisa expor um objeto gerenciado de um objeto não gerenciado.
  • Foo (NSEmptyFlag): isso é usado por classes derivadas para impedir a inicialização dupla.

Para construtores que você definir, 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 Construtor. Por exemplo, para vincular o initWithFrame: construtor, isso é o que você usaria:

[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);

Protocolos de vinculaçã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 vinculada 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 é vinculada no Xamarin.iOS:

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
        [Export ("accelerometer:didAccelerate:")]
        void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}

Uma vez que este é um método opcional na definição para UIAccelerometerDelegate não há mais nada a fazer. Mas se houver um método necessário no protocolo, você deve adicionar o [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 geralmente é feito atribuindo Objective-C à propriedade "delegate" uma instância de um objeto que responde aos métodos no protocolo.

A convenção no Xamarin.iOS é oferecer suporte Objective-C ao estilo fracamente acoplado 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 que é fortemente tipada e uma WeakDelegate que é vagamente tipada. Geralmente vinculamos a versão de tipo flexível com [Export], e usamos o [Wrap] atributo para fornecer a versão fortemente tipada.

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

Novo 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 ter que usar as subclasses separadas das classes de modelo abstratas anteriores.

Qualquer definição que contenha o [Protocol] atributo realmente gerará 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 que você pode substituir métodos individuais e obter segurança de tipo completo. Mas devido ao C# não suportar várias heranças, há cenários em que você pode precisar ter uma classe base diferente, mas você ainda deseja implementar uma interface, que é onde o

A definição de interface gerada entra. É uma interface que tem todos os métodos necessários do protocolo. Isso permite que os desenvolvedores que desejam implementar seu protocolo simplesmente implementem a interface. O tempo de execução 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 adotarem o protocolo obterão verificação de tipo completo para os métodos necessários, mas terão que recorrer à digitação fraca (usando [Export] manualmente atributos e combinando a assinatura) para os métodos de protocolo opcionais.

Para tornar conveniente consumir uma API que usa protocolos, a ferramenta de vinculaçã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, será necessário escrever interfaces vazias de esqueleto em sua definição de API. Se você quiser usar o MyProtocol em uma API, será necessário 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 vinculaçã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 para os métodos de interface necessários é exportada com o nome próprio, portanto, é equivalente a isto:

class MyDelegate : NSObject, IUITableViewDelegate {
    [Export ("getRowHeight:")]
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

Isso funcionará para todos os membros do protocolo necessários, mas há um caso especial com seletores opcionais a serem observados. 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 de protocolo é necessário adicionar o [Export]. O IDE irá adicioná-lo via 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) só respondem aos seletores exatos fornecidos.

Algumas classes raras podem se comportar de forma diferente aqui. Em quase todos os casos, no entanto, é seguro usar qualquer um.

Extensões de classe de vinculaçã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 está presente, você pode usar o [BaseType] para sinalizar o método como sendo o receptor da Objective-C mensagem.

Por exemplo, no Xamarin.iOS, vinculamos os métodos de extensão que são definidos em NSString quando UIKit é importado como métodos no , como este NSStringDrawingExtensions:

[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
    [Export ("drawAtPoint:withFont:")]
    CGSize DrawString (CGPoint point, UIFont font);
}

Listas de argumentos de vinculação Objective-C

Objective-C suporta 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 cadeias de caracteres ou valores inteiros que devem ser referenciados. Eles são comumente usados como cadeia de caracteres que representam 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ê deseja encapsular vários campos em uma classe estática que não deriva do 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 de NSObject e conterá uma ligação com o NSSomeEventNotificationNSString 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)
  • Flutuadores de 32 bits (System.Single)
  • Flutuadores 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á nenhuma biblioteca para vincular, então você precisa usar o __Internal nome:

[Static]
interface LonelyClass {
    [Field ("MyFieldFromALibrary", "__Internal")]
    NSString MyFieldFromALibrary { get; }
}

Enums de ligação

Você pode adicionar enum diretamente em seus arquivos de vinculação para facilitar o uso deles dentro das definições de API - sem usar um arquivo de origem diferente (que precisa ser compilado nas ligaçõ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 seus próprios enums para substituir NSString constantes. Nesse caso, o gerador criará automaticamente os métodos para converter valores enums 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 vinculantes.

No entanto, isso limitaria a subclassificação do tipo, pois a alternativa de API mais agradável usa um [Wrap] atributo. Esses métodos gerados não virtualsã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, como [Protected]. Isso permitirá que a subclassificação funcione, quando necessário, e a versão encapsulada ainda funcionará e chamará o método substituído.

Vinculação NSValue, NSNumbere NSString para um tipo melhor

O [BindAs] atributo permite a vinculação NSNumbere NSValueNSString(enums) 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 de retorno), 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);

Saída:

[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }

Internamente faremos as bool?<-NSNumber> e<CGRect ->NSValue conversões.

[BindAs] também suporta matrizes de NSNumberNSValue e NSString(enums).

Por exemplo:

[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }

Saída:

[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }

CAScroll é um NSString enum com suporte, vamos buscar o valor certo NSString e lidar com a conversão de tipo.

Consulte a documentação para ver os [BindAs] tipos de conversão suportados.

Notificações vinculativas

As notificações são mensagens que são postadas NSNotificationCenter.DefaultCenter no e são 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 na central de notificações, ele normalmente contém uma carga armazenada no dicionário NSNotification.UserInfo . Este dicionário é fracamente digitado, 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 é usada como um booleano também.

O gerador de vinculação Xamarin.iOS fornece suporte para desenvolvedores vincularem notificações. Para fazer isso, defina o [Notification] em uma propriedade que também foi marcada com um [Field] propriedade (pode ser pública ou privada).

Esse atributo pode ser usado sem argumentos para notificações que não carregam carga útil ou você pode especificar uma que faça referência a System.Type outra interface na definição da API, normalmente com o nome terminando com "EventArgs". O gerador transformará a interface em uma classe que subclasses 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 procurar 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, como esta:

token.Dispose ();

Ou você pode chamar NSNotification.DefaultCenter.RemoveObserver e passar o token. Se a 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 irá gerar uma MyScreenChangedEventArgs classe com as ScreenX propriedades e ScreenY que irá 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 sondar se a chave estiver 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 vinculativas

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 é vinculada (por exemplo UIKit), disponibilizando seus métodos, mas apenas 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#. Esta é a aparência de uma categoria 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 vinculá-los, você pode usar o [Category] atributo em uma definição de interface. Ao usar o [Category] atributo, o significado do [BaseType] alterações de atributo de ser usado para especificar a classe base a ser estendida, para ser o tipo a ser estendido.

A seguir mostra como as UIView extensões são vinculadas 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 método de MakeBackgroundRed extensão. Isso significa que agora você pode chamar "MakeBackgroundRed" em qualquer UIView subclasse, dando-lhe a mesma funcionalidade que você obteria no Objective-C. Em alguns outros casos, as categorias são usadas não para estender uma classe do 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ê pode muito bem adicioná-las todas à definição de classe. Ambos conseguiriam 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);
}

É apenas mais curto nesses casos para 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 vinculação

Blocos são uma nova construção introduzida pela Apple para trazer o equivalente funcional de métodos anônimos C# para .Objective-C 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 em C#, pois tem suporte para capturar o ambiente atual (o ponteiro "this", acesso a variáveis e parâmetros locais). O método acima em NSSet invoca o bloco com dois parâmetros: um NSObject (a id obj parte) e um ponteiro para uma parte booleana (a BOOL *stop) .

Para vincular esse tipo de API ao btouch, você precisa primeiro declarar a assinatura do tipo de bloco como um delegado C# e, em seguida, fazer referência a ela a partir de um ponto de entrada da API, da seguinte 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 vinculação pode transformar uma determinada classe de métodos em métodos async-friendly (métodos que retornam uma Task ou Task<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 vinculação gerará uma versão desse método com o sufixo Async. Se o retorno de chamada não tiver parâmetros, o valor de retorno será um Task, se o retorno de chamada usar um parâmetro, o resultado será um Task<T>arquivo . Se o retorno de chamada tiver vários parâmetros, você deverá definir o ResultType ou ResultTypeName especificar o nome desejado do tipo gerado que manterá 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);

Sobrepondo tipos fortes para parâmetros NSDictionary fracos

Em muitos lugares da 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 os bastidores das várias chaves e valores subjacentes.

Então, por exemplo, se a Objective-C API aceitou um NSDictionary e está documentado como tomando a chave XyzVolumeKey que leva um NSNumber com um valor de volume de 0,0 a 1,0 e um XyzCaptionKey que leva uma cadeia de caracteres, você gostaria que seus usuários tivessem uma API agradável que se parece com isto:

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 não exige que esses dicionários tenham o valor, portanto, há cenários em Objective-C que o valor pode não ser definido.

Para fazer isso, você precisa fazer algumas coisas:

  • Crie uma classe fortemente tipada, que subclasses DictionaryContainer e fornece os vários getters e setters para cada propriedade.
  • Declare sobrecargas para os métodos que levam NSDictionary para tomar 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 seu contrato. Isso é 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 apareça na 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 Atributo [Internal].

Como você pode ver, usamos o [Wrap] para exibir um novo ponto de entrada da API e nós o afloramos usando nossa classe fortemente tipada XyzOptions . O método wrapper também permite que null seja passado.

Agora, uma coisa que a gente não mencionou é de onde vieram os XyzOptionsKeys valores. Normalmente, você agruparia as chaves que uma API apresenta em uma classe estática como , da XyzOptionsKeysseguinte forma:

[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 muita coisa clichê 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 sua interface que derivará e DictionaryContainer fornecerá acessadores digitados fortes para ela.

O [StrongDictionary] atributo usa um parâmetro, que é o nome da classe estática que contém as chaves do 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 de ter que pesquisar as chaves manualmente.

Esta é a aparência de toda a sua vinculação:

[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 referenciar em seus XyzOption membros um campo diferente (que não é o nome do imóvel com o sufixo Key), você pode decorar o imóvel com um [Export] com o nome que você deseja usar.

Tipo: mapeamentos

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 Xamarin.iOS
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString (leia mais sobre o NSString vinculante) string
char * string (veja também: [PlainString])
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
Tipos de 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 tempo de execução do Xamarin.iOS cuida automaticamente da conversão de matrizes C# para NSArrays e fazer a conversão de volta, portanto, 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 autocompletar código adequado com o tipo real sem forçar o usuário a adivinhar ou procurar 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 de retorno.

Seletores

Os seletores aparecem na Objective-C API como o tipo SELespecial . Ao vincular 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 para invocar no objeto de destino. Fornecer ambos corresponde basicamente ao delegado C#: algo que encapsula tanto o método a ser invocado quanto o objeto no qual invocar o método.

Esta é a aparência da vinculaçã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 vinculação mais agradável aos desenvolvedores de C#, você normalmente fornecerá um método que usa um NSAction parâmetro, que permite que delegados de C# e lambdas 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);
     }
}

Portanto, 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

Quando você está vinculando 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 de API no NSString .

Em alguns casos raros, uma API pode expor uma cadeia de caracteres semelhante a C (char *) em vez de uma Objective-C cadeia de caracteres (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 a seguinte 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 um passe por referência, em vez de um valor de saída puro.

Sua vinculaçã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". As semânticas disponíveis são:

  • Atribuir
  • Copiar
  • Manter

Diretrizes de estilo

Usando [Interno]

Você pode usar o [Internal] atributo 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 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 tiver limitações no gerador de vinculação, por exemplo, alguns cenários avançados podem expor tipos que não estão vinculados e você deseja vincular à sua própria maneira, e você mesmo deseja encapsular esses tipos à sua própria 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 delegada).

Este modelo, embora totalmente suportado e apresentado pelo Xamarin.iOS às vezes pode ser complicado. O Xamarin.iOS expõe o padrão de eventos 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 ligaçã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 ligações para um representante específico Objective-C e expor o delegado como eventos e propriedades C# no tipo de host.

Há duas classes envolvidas nesse processo, a classe host que será a que atualmente emite eventos e os envia para a Delegate classe ou 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 de host, adicione à sua [BaseType]
    o tipo que está agindo como seu delegado e o nome C# que você expôs. No nosso exemplo acima, esses são typeof (MyClassDelegate) e WeakDelegate respectivamente.
  • Em sua classe delegate, 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 está limitado a encapsular apenas um único destino de evento, é possível que algumas Objective-C classes emitam mensagens para mais de um delegado, então 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 EventArgs nome da 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 as chamadas de evento, 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 só podem ter um único assinante.

O processo é idêntico, a única diferença é que, em vez de expor o EventArgs nome da classe que será gerada, o EventArgs realmente é usado para nomear o nome do delegado C# resultante.

Se o método na classe delegate retornar um valor, o gerador de vinculação mapeará isso em um método delegate 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 de retorno, 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 principais em um arquivo separado e inclua isso como parte de um dos arquivos extras que você fornece ao btouch.

Vinculando as dependências

Se você estiver vinculando APIs que não fazem parte do seu aplicativo, será necessário certificar-se de 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 compilação para invocar o mtouch comando com alguns argumentos de compilação extras que especificam como vincular com as novas bibliotecas usando a opção "-gcc_flags", seguido por uma cadeia de caracteres entre aspas que contém todas as bibliotecas extras que são necessárias para o seu programa, Assim:

-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"

O exemplo acima vinculará libMyLibrary.ao e libSystemLibrary.dylib a biblioteca de CFNetwork estrutura ao executável final.

Ou você pode aproveitar o nível [LinkWithAttribute]de montagem , que você pode incorporar 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 sua vinculaçã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 a razão é que o sinalizador -ObjC, embora compile o código, ele não preserva os metadados necessários para suportar categorias (a eliminação de código morto do vinculador/compilador o remove) que você precisa em tempo de execução 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 vinculação pode ajudar um pouco aqui.

Por exemplo, se você tivesse uma classe que mostrasse uma mensagem e, em seguida, 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 próprio objeto e vazar ou limpar ativamente a referência para a caixa por conta própria. Enquanto o código de vinculação, o gerador suporta manter o controle da referência para você e limpá-lo 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 tirar proveito disso, 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 concluiu seu trabalho, da seguinte forma:

[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, oferecemos suporte à herança de protocolos que foram marcados com a [Model] propriedade. Isso é útil em certos 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, precisávamos copiar o protocolo para cada implementação, mas nesses casos agora podemos ter a MKShape classe herdada do MKOverlay protocolo e ela gerará todos os métodos necessários automaticamente.