Xamarin.iOS API Design

Além das principais Bibliotecas de Classes Base que fazem parte do Mono, o Xamarin.iOS é fornecido com associações para várias APIs do iOS para permitir que os desenvolvedores criem aplicativos iOS nativos com Mono.

No núcleo do Xamarin.iOS, há um mecanismo de interoperabilidade que une o mundo C# com o Objective-C mundo e associações para as APIs baseadas em C do iOS, como CoreGraphics e OpenGL ES.

O runtime de baixo nível para se comunicar com Objective-C o código está em MonoTouch.ObjCRuntime. Além disso, são fornecidas associações para Foundation, CoreFoundation e UIKit .

Princípios de design

Esta seção detalha alguns de nossos princípios de design para as associações Xamarin.iOS (elas também se aplicam ao Xamarin.Mac, as associações Mono para Objective-C no macOS):

  • Siga as Diretrizes de Design da Estrutura

  • Permitir que os desenvolvedores subclasse Objective-C classes:

    • Derivar de uma classe existente
    • Chamar o construtor base para encadear
    • Os métodos de substituição devem ser feitos com o sistema de substituição do C#
    • A subclasse deve funcionar com constructos padrão C#
  • Não expor desenvolvedores a Objective-C seletores

  • Fornecer um mecanismo para chamar bibliotecas arbitrárias Objective-C

  • Tornar tarefas comuns fáceis Objective-C e difíceis Objective-C possíveis

  • Expor Objective-C propriedades como propriedades C#

  • Expor uma API fortemente tipada:

    • Aumentar a segurança do tipo
    • Minimizar erros de runtime
    • Obter o IDE IntelliSense em tipos de retorno
    • Permite a documentação pop-up do IDE
  • Incentive a exploração no IDE das APIs:

    • Por exemplo, em vez de expor uma matriz de tipo fraco, desta forma:

      NSArray *getViews
      

      Exponha um tipo forte, desta forma:

      NSView [] Views { get; set; }
      

      O uso de tipos fortes fornece Visual Studio para Mac a capacidade de fazer o preenchimento automático durante a navegação na API, disponibiliza todas as System.Array operações no valor retornado e permite que o valor retornado participe do LINQ.

  • Tipos nativos de C#:

    • NSString Fica string

    • Transformar int parâmetros e uint que deveriam ter sido enumerações em enumerações C# e enumerações C# com [Flags] atributos

    • Em vez de objetos neutros NSArray de tipo, exponha matrizes como matrizes fortemente tipada.

    • Para eventos e notificações, dê aos usuários uma opção entre:

      • Uma versão fortemente tipada por padrão
      • Uma versão com tipo fraco para casos de uso avançados
  • Suporte ao Objective-C padrão delegado:

    • Sistema de eventos C#
    • Expor delegados C# (lambdas, métodos anônimos e System.Delegate) a Objective-C APIs como blocos

Assemblies

O Xamarin.iOS inclui muitos assemblies que constituem o perfil do Xamarin.iOS. A página Assemblies tem mais informações.

Namespaces principais

ObjCRuntime

O namespace ObjCRuntime permite que os desenvolvedores conectem os mundos entre C# e Objective-C. Essa é uma nova associação, projetada especificamente para o iOS, com base na experiência de Cocoa# e Gtk#.

Foundation

O namespace Foundation fornece os tipos de dados básicos projetados para interoperar com a Objective-C estrutura Foundation que faz parte do iOS e é a base para programação orientada a objetos no Objective-C.

O Xamarin.iOS espelha em C# a hierarquia de classes de Objective-C. Por exemplo, a Objective-C classe base NSObject é utilizável de C# via Foundation.NSObject.

Embora o namespace Foundation forneça associações para os tipos base subjacentes Objective-C , em alguns casos mapeamos os tipos subjacentes para tipos .NET. Por exemplo:

  • Em vez de lidar com NSString e NSArray, o runtime os expõe como cadeias de caracteres C# se matrizesfortemente tipada em toda a API.

  • Várias APIs auxiliares são expostas aqui para permitir que os desenvolvedores associem APIs de Objective-C terceiros, outras APIs do iOS ou APIs que não estão vinculadas atualmente pelo Xamarin.iOS.

Para obter mais informações sobre APIs de associação, consulte a seção Gerador de Associação do Xamarin.iOS .

NSObject

O tipo NSObject é a base para todas as Objective-C associações. Os tipos Xamarin.iOS espelho duas classes de tipos das APIs CocoaTouch do iOS: os tipos C (normalmente chamados de tipos CoreFoundation) e os Objective-C tipos (que derivam da classe NSObject).

Para cada tipo que espelha um tipo não gerenciado, é possível obter o objeto nativo por meio da propriedade Handle .

Embora o Mono forneça coleta de lixo para todos os seus objetos, o Foundation.NSObject implementa a interface System.IDisposable . Você pode liberar explicitamente os recursos de qualquer NSObject determinado sem precisar esperar que o Coletor de Lixo seja iniciado. A liberação explícita de recursos é importante quando você está usando NSObjects pesados, por exemplo, UIImages que podem conter ponteiros para grandes blocos de dados.

Se o tipo precisar executar a finalização determinística, substitua o método NSObject.Dispose(bool) O parâmetro para Dispose é "bool disposing" e, se definido como true, significa que o método Dispose está sendo chamado porque o usuário chamou explicitamente Dispose () no objeto. Um valor falso significa que o método Dispose(bool disposing) está sendo chamado do finalizador no thread do finalizador.

Categorias

A partir do Xamarin.iOS 8.10, é possível criar Objective-C categorias do C#.

Isso é feito usando o Category atributo , especificando o tipo a ser estendido como um argumento para o atributo . O exemplo a seguir, por exemplo, estenderá NSString.

[Category (typeof (NSString))]

Cada método de categoria está usando o mecanismo normal para exportar métodos para Objective-C usar o Export atributo :

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

Todos os métodos de extensão gerenciados devem ser estáticos, mas é possível criar Objective-C métodos de instância usando a sintaxe padrão para métodos de extensão em C#:

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

e o primeiro argumento para o método de extensão será a instância na qual o método foi invocado.

Exemplo completo:

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

Este exemplo adicionará um método nativo de instância toUpper à classe NSString, que pode ser invocada de Objective-C.

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

Um cenário em que isso é útil é adicionar um método a um conjunto inteiro de classes em sua base de código, por exemplo, isso faria com que todas as UIViewController instâncias relatassem que elas podem girar:

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

PreserveAttribute é um atributo personalizado usado para informar mtouch – a ferramenta de implantação do Xamarin.iOS – para preservar um tipo ou um membro de um tipo durante a fase em que o aplicativo é processado para reduzir seu tamanho.

Cada membro que não está vinculado estaticamente pelo aplicativo está sujeito a ser removido. Portanto, esse atributo é usado para marcar membros que não são referenciados estaticamente, mas que ainda são necessários para seu aplicativo.

Por exemplo, se você cria uma instância de tipos dinamicamente, convém preservar o construtor padrão de seus tipos. Se você usar a serialização de XML, você talvez queira preservar as propriedades de seus tipos.

Você pode aplicar esse atributo a todos os membros de um tipo ou ao tipo propriamente dito. Se você quiser preservar o tipo inteiro, poderá usar a sintaxe [Preserve (AllMembers = true)] no tipo .

UIKit

O namespace do UIKit contém um mapeamento um-para-um para todos os componentes da interface do usuário que compõem o CocoaTouch na forma de classes C#. A API foi modificada para seguir as convenções usadas na linguagem C#.

Delegados C# são fornecidos para operações comuns. Para obter mais informações, consulte a seção delegados .

OpenGLES

Para o OpenGLES, distribuimos uma versão modificada da API do OpenTK , uma associação orientada a objeto para OpenGL que foi modificada para usar estruturas e tipos de dados CoreGraphics e apenas expor a funcionalidade disponível no iOS.

A funcionalidade openGLES 1.1 está disponível por meio do tipo ES11.GL.

A funcionalidade OpenGLES 2.0 está disponível por meio do tipo ES20.GL.

A funcionalidade do OpenGLES 3.0 está disponível por meio do tipo ES30.GL.

Design de associação

O Xamarin.iOS não é apenas uma associação à plataforma subjacente Objective-C . Ele estende o sistema de tipos .NET e despacha o sistema para misturar melhor C# e Objective-C.

Assim como P/Invoke é uma ferramenta útil para invocar bibliotecas nativas no Windows e no Linux ou como o suporte do IJW pode ser usado para interoperabilidade COM no Windows, o Xamarin.iOS estende o runtime para dar suporte à associação de objetos C# a Objective-C objetos.

A discussão nas próximas seções não é necessária para usuários que estão criando aplicativos Xamarin.iOS, mas ajudará os desenvolvedores a entender como as coisas são feitas e os ajudará ao criar aplicativos mais complicados.

Tipos

Quando fazia sentido, os tipos C# são expostos em vez de tipos foundation de baixo nível, para o universo C#. Isso significa que a API usa o tipo "cadeia de caracteres" C# em vez de NSString e usa matrizes C# fortemente tipada em vez de expor NSArray.

Em geral, no design Xamarin.iOS e Xamarin.Mac, o objeto subjacente NSArray não é exposto. Em vez disso, o runtime converte NSArrayautomaticamente s em matrizes fortemente tipada de alguma NSObject classe. Portanto, o Xamarin.iOS não expõe um método de tipo fraco como GetViews para retornar um NSArray:

NSArray GetViews ();

Em vez disso, a associação expõe um valor retornado fortemente tipado, assim:

UIView [] GetViews ();

Há alguns métodos expostos em , para os casos de canto em NSArrayque talvez você queira usar um NSArray diretamente, mas seu uso é desencorajado na associação de API.

Além disso, na API Clássica em vez de expor CGRect, CGPointe CGSize da API CoreGraphics, as substituímos pelas System.Drawing implementações RectangleF, PointFe SizeF como elas ajudariam os desenvolvedores a preservar o código OpenGL existente que usa OpenTK. Ao usar a nova API Unificada de 64 bits, a API CoreGraphics deve ser usada.

Herança

O design da API do Xamarin.iOS permite que os desenvolvedores estendam tipos nativos Objective-C da mesma forma que estenderiam um tipo C#, usando a palavra-chave de "substituição" em uma classe derivada e encadeando até a implementação base usando a palavra-chave C# "base".

Esse design permite que os desenvolvedores evitem lidar com Objective-C seletores como parte de seu processo de desenvolvimento, pois todo Objective-C o sistema já está encapsulado dentro das bibliotecas do Xamarin.iOS.

Tipos e Construtor de Interfaces

Ao criar classes .NET que são instâncias de tipos criados pelo Construtor de Interfaces, você precisa fornecer um construtor que usa um único IntPtr parâmetro. Isso é necessário para associar a instância do objeto gerenciado ao objeto não gerenciado. O código consiste em uma única linha, como esta:

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

Delegados

Objective-C e C# têm significados diferentes para a palavra delegado em cada idioma.

Objective-C No mundo e na documentação que você encontrará online sobre CocoaTouch, um delegado normalmente é uma instância de uma classe que responderá a um conjunto de métodos. Isso é semelhante a uma interface C#, com a diferença sendo que os métodos nem sempre são obrigatórios.

Esses delegados desempenham um papel importante no UIKit e em outras APIs CocoaTouch. Eles são usados para realizar várias tarefas:

  • Para fornecer notificações ao código (semelhante à entrega de eventos em C# ou Gtk+).
  • Para implementar modelos para controles de visualização de dados.
  • Para conduzir o comportamento de um controle.

O padrão de programação foi projetado para minimizar a criação de classes derivadas para alterar o comportamento de um controle. Essa solução é semelhante ao que outros kits de ferramentas de GUI fizeram ao longo dos anos: sinais do Gtk, slots de Qt, eventos winforms, eventos WPF/Silverlight e assim por diante. Para evitar ter centenas de interfaces (uma para cada ação) ou exigir que os desenvolvedores implementem muitos métodos de que não precisam, Objective-C o dá suporte a definições de método opcionais. Isso é diferente das interfaces C# que exigem que todos os métodos sejam implementados.

Nas Objective-C classes, você verá que as classes que usam esse padrão de programação expõem uma propriedade, chamada delegate, que é necessária para implementar as partes obrigatórias da interface e zero, ou mais, das partes opcionais.

No Xamarin.iOS, três mecanismos mutuamente exclusivos para associar a esses delegados são oferecidos:

  1. Por meio de eventos.
  2. Fortemente tipado por meio de uma Delegate propriedade
  3. Digitado livremente por meio de uma WeakDelegate propriedade

Por exemplo, considere a classe UIWebView. Isso é enviado para uma instância UIWebViewDelegate, que é atribuída à propriedade delegate.

Por meio de eventos

Para muitos tipos, o Xamarin.iOS criará automaticamente um delegado apropriado, o que encaminhará as UIWebViewDelegate chamadas para eventos C#. Para UIWebView:

Por exemplo, este programa simples registra os horários de início e término ao carregar uma exibição da Web:

DateTime startTime, endTime;
var web = new UIWebView (new CGRect (0, 0, 200, 200));
web.LoadStarted += (o, e) => startTime = DateTime.Now;
web.LoadFinished += (o, e) => endTime = DateTime.Now;
Por meio de propriedades

Os eventos são úteis quando pode haver mais de um assinante no evento. Além disso, os eventos são limitados a casos em que não há nenhum valor retornado do código.

Para casos em que o código deve retornar um valor, optamos por propriedades. Isso significa que apenas um método pode ser definido em um determinado momento em um objeto .

Por exemplo, você pode usar esse mecanismo para ignorar o teclado na tela do manipulador para um UITextField:

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

A UITextFieldpropriedade de Neste ShouldReturn caso usa como argumento um delegado que retorna um valor bool e determina se o TextField deve fazer algo com o botão Retornar sendo pressionado. Em nosso método, retornamos true para o chamador, mas também removemos o teclado da tela (isso acontece quando o campo de texto chama ResignFirstResponder).

Fortemente tipado por meio de uma propriedade Delegate

Se preferir não usar eventos, você poderá fornecer sua própria subclasse UIWebViewDelegate e atribuí-la à propriedade UIWebView.Delegate . Depois que UIWebView.Delegate tiver sido atribuído, o mecanismo de expedição de eventos UIWebView não funcionará mais e os métodos UIWebViewDelegate serão invocados quando os eventos correspondentes ocorrerem.

Por exemplo, esse tipo simples registra o tempo necessário para carregar uma exibição da Web:

class Notifier : UIWebViewDelegate  {
    DateTime startTime, endTime;

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

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

O acima é usado em um código como este:

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

O acima criará um UIWebViewer e o instruirá a enviar mensagens para uma instância do Notifier, uma classe que criamos para responder às mensagens.

Esse padrão também é usado para controlar o comportamento de determinados controles, por exemplo, no caso UIWebView, a propriedade UIWebView.ShouldStartLoad permite que a UIWebView instância controle se o UIWebView carregará uma página ou não.

O padrão também é usado para fornecer os dados sob demanda para alguns controles. Por exemplo, o controle UITableView é um poderoso controle de renderização de tabela e a aparência e o conteúdo são controlados por uma instância de um UITableViewDataSource

Digitado livremente por meio da propriedade WeakDelegate

Além da propriedade fortemente tipada, também há um delegado de tipo fraco que permite que o desenvolvedor associe as coisas de forma diferente, se desejado. Em todos os lugares em que uma propriedade fortemente tipada Delegate é exposta na associação do Xamarin.iOS, uma propriedade correspondente WeakDelegate também é exposta.

Ao usar o WeakDelegate, você é responsável por decorar corretamente sua classe usando o atributo Exportar para especificar o seletor. Por exemplo:

class Notifier : NSObject  {
    DateTime startTime, endTime;

    [Export ("webViewDidStartLoad:")]
    public void LoadStarted (UIWebView webview)
    {
        startTime = DateTime.Now;
    }

    [Export ("webViewDidFinishLoad:")]
    public void LoadingFinished (UIWebView webView)
    {
        endTime= DateTime.Now;
    }
}

[...]

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

Depois que a WeakDelegate propriedade tiver sido atribuída, a Delegate propriedade não será usada. Além disso, se você implementar o método em uma classe base herdada que deseja [Exportar], deverá torná-lo um método público.

Mapeamento do Objective-C padrão delegado para C#

Quando você vê Objective-C exemplos semelhantes a este:

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

Isso instrui o idioma a criar e construir uma instância da classe "SomethingDelegate" e atribuir o valor à propriedade delegate na variável foo. Esse mecanismo é compatível com xamarin.iOS e C# a sintaxe é:

foo.Delegate = new SomethingDelegate ();

No Xamarin.iOS, fornecemos classes fortemente tipadas que são mapeadas para as Objective-C classes delegadas. Para usá-los, você estará subclasse e substituindo os métodos definidos pela implementação do Xamarin.iOS. Para obter mais informações sobre como eles funcionam, consulte a seção "Modelos" abaixo.

Mapeando delegados para C#

O UIKit em geral usa delegados Objective-C em duas formas.

O primeiro formulário fornece uma interface para o modelo de um componente. Por exemplo, como um mecanismo para fornecer dados sob demanda para uma exibição, como o recurso de armazenamento de dados para uma exibição de Lista. Nesses casos, você sempre deve criar uma instância da classe adequada e atribuir a variável.

No exemplo a seguir, fornecemos o UIPickerView com uma implementação para um modelo que usa cadeias de caracteres:

public class SampleTitleModel : UIPickerViewTitleModel {

    public override string TitleForRow (UIPickerView picker, nint row, nint component)
    {
        return String.Format ("At {0} {1}", row, component);
    }
}

[...]

pickerView.Model = new MyPickerModel ();

O segundo formulário é fornecer notificação para eventos. Nesses casos, embora ainda exponhamos a API no formulário descrito acima, também fornecemos eventos C#, que devem ser mais simples de usar para operações rápidas e integrados a delegados anônimos e expressões lambda em C#.

Por exemplo, você pode assinar UIAccelerometer eventos:

UIAccelerometer.SharedAccelerometer.Acceleration += (sender, args) => {
   UIAcceleration acc = args.Acceleration;
   Console.WriteLine ("Time={0} at {1},{2},{3}", acc.Time, acc.X, acc.Y, acc.Z);
}

As duas opções estão disponíveis onde fazem sentido, mas como programador, você deve escolher uma ou outra. Se você criar sua própria instância de um respondente/delegado fortemente tipado e atribuí-la, os eventos C# não estarão funcionais. Se você usar os eventos C#, os métodos na classe respondente/delegado nunca serão chamados.

O exemplo anterior usado UIWebView pode ser escrito usando lambdas C# 3.0 como esta:

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

Respondendo a eventos

No Objective-C código, às vezes, os manipuladores de eventos para vários controles e provedores de informações para vários controles serão hospedados na mesma classe. Isso é possível porque as classes respondem às mensagens e, enquanto as classes responderem às mensagens, é possível vincular objetos.

Conforme detalhado anteriormente, o Xamarin.iOS dá suporte ao modelo de programação baseado em eventos C# e ao Objective-C padrão delegado, em que você pode criar uma nova classe que implementa o delegado e substitui os métodos desejados.

Também é possível dar suporte Objective-Cao padrão em que os respondentes de várias operações diferentes estão hospedados na mesma instância de uma classe. Para fazer isso, porém, você precisará usar recursos de baixo nível da associação Xamarin.iOS.

Por exemplo, se você quisesse que sua classe respondesse à UITextFieldDelegate.textFieldShouldClearmensagem : e ao UIWebViewDelegate.webViewDidStartLoad: na mesma instância de uma classe, você teria que usar a declaração de atributo [Exportar]:

public class MyCallbacks : NSObject {
    [Export ("textFieldShouldClear:"]
    public bool should_we_clear (UITextField tf)
    {
        return true;
    }

    [Export ("webViewDidStartLoad:")]
    public void OnWebViewStart (UIWebView view)
    {
        Console.WriteLine ("Loading started");
    }
}

Os nomes C# para os métodos não são importantes; tudo o que importa são as cadeias de caracteres passadas para o atributo [Exportar].

Ao usar esse estilo de programação, verifique se os parâmetros C# correspondem aos tipos reais que o mecanismo de runtime passará.

Modelos

Em instalações de armazenamento UIKit ou em respondentes implementados usando classes auxiliares, eles são referenciados no Objective-C código como delegados e são implementados como protocolos.

Objective-C Os protocolos são como interfaces, mas dão suporte a métodos opcionais, ou seja, nem todos os métodos precisam ser implementados para que o protocolo funcione.

Há duas maneiras de implementar um modelo. Você pode implementá-lo manualmente ou usar as definições fortemente tipada existentes.

O mecanismo manual é necessário quando você tenta implementar uma classe que não foi associada pelo Xamarin.iOS. É fácil fazer:

  • Sinalizar sua classe para registro com o runtime
  • Aplicar o atributo [Exportar] com o nome real do seletor em cada método que você deseja substituir
  • Instancie a classe e passe-a.

Por exemplo, o seguinte implementa apenas um dos métodos opcionais na definição do protocolo UIApplicationDelegate:

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

O Objective-C nome do seletor ("applicationDidFinishLaunching:") é declarado com o atributo Export e a classe é registrada com o [Register] atributo .

O Xamarin.iOS fornece declarações fortemente tipada, prontas para uso, que não exigem associação manual. Para dar suporte a esse modelo de programação, o runtime do Xamarin.iOS dá suporte ao atributo [Model] em uma declaração de classe. Isso informa ao runtime que ele não deve conectar todos os métodos na classe , a menos que os métodos sejam implementados explicitamente.

Isso significa que, no UIKit, as classes que representam um protocolo com métodos opcionais são escritas da seguinte maneira:

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

Quando você deseja implementar um modelo que implementa apenas alguns dos métodos, tudo o que você precisa fazer é substituir os métodos nos quais você está interessado e ignorar os outros métodos. O runtime conectará apenas os métodos substituídos, não os métodos originais ao Objective-C mundo.

O equivalente ao exemplo manual anterior é:

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

As vantagens são que não é necessário investigar os Objective-C arquivos de cabeçalho para localizar o seletor, os tipos dos argumentos ou o mapeamento para C#, e que você obtém o intelliSense de Visual Studio para Mac, juntamente com tipos fortes

Saídas XIB e C#

Essa é uma descrição de baixo nível de como os Outlets se integram ao C# e é fornecida para usuários avançados do Xamarin.iOS. Ao usar Visual Studio para Mac, o mapeamento é feito automaticamente nos bastidores usando o código gerado na versão de pré-lançamento para você.

Ao projetar sua interface do usuário com o Interface Builder, você só projetará a aparência do aplicativo e estabelecerá algumas conexões padrão. Se você quiser buscar informações programaticamente, alterar o comportamento de um controle em runtime ou modificar o controle em runtime, será necessário associar alguns dos controles ao código gerenciado.

Isso é feito em algumas etapas:

  1. Adicione a declaração de saída ao proprietário do arquivo.
  2. Conecte o controle ao proprietário do Arquivo.
  3. Armazene a interface do usuário mais as conexões no arquivo XIB/NIB.
  4. Carregue o arquivo NIB em runtime.
  5. Acesse a variável de saída.

As etapas (1) a (3) são abordadas na documentação da Apple para criar interfaces com o Interface Builder.

Ao usar o Xamarin.iOS, seu aplicativo precisará criar uma classe derivada de UIViewController. Ele é implementado da seguinte maneira:

public class MyViewController : UIViewController {
    public MyViewController (string nibName, NSBundle bundle) : base (nibName, bundle)
    {
        // You can have as many arguments as you want, but you need to call
        // the base constructor with the provided nibName and bundle.
    }
}

Em seguida, para carregar o ViewController de um arquivo NIB, faça o seguinte:

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

Isso carrega a interface do usuário do NIB. Agora, para acessar as saídas, é necessário informar ao runtime que queremos acessá-las. Para fazer isso, a UIViewController subclasse precisa declarar as propriedades e anotá-las com o atributo [Connect]. Dessa forma:

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

A implementação da propriedade é aquela que realmente busca e armazena o valor para o tipo nativo real.

Você não precisa se preocupar com isso ao usar Visual Studio para Mac e InterfaceBuilder. Visual Studio para Mac espelha automaticamente todas as saídas declaradas com código em uma classe parcial que é compilada como parte do projeto.

Seletores

Um conceito básico de Objective-C programação são os seletores. Muitas vezes, você encontrará APIs que exigem que você passe um seletor ou espera que seu código responda a um seletor.

Criar novos seletores em C# é fácil – basta criar uma nova instância da ObjCRuntime.Selector classe e usar o resultado em qualquer lugar na API que a exija. Por exemplo:

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

Para que um método C# responda a uma chamada de seletor, ele deve herdar do NSObject tipo e o método C# deve ser decorado com o nome do seletor usando o [Export] atributo . Por exemplo:

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

Os nomes dos seletores devem corresponder exatamente, incluindo todos os dois-pontos intermediários e à direita (":"), se estiverem presentes.

Construtores NSObject

A maioria das classes no Xamarin.iOS que derivam de NSObject exporá construtores específicos à funcionalidade do objeto, mas também exporão vários construtores que não são imediatamente óbvios.

Os construtores são usados da seguinte maneira:

public Foo (IntPtr handle)

Esse construtor é usado para instanciar sua classe quando o runtime precisa mapear sua classe para uma classe não gerenciada. Isso acontece quando você carrega um arquivo XIB/NIB. Neste ponto, o Objective-C runtime terá criado um objeto no mundo não gerenciado e esse construtor será chamado para inicializar o lado gerenciado.

Normalmente, tudo o que você precisa fazer é chamar o construtor base com o parâmetro handle e, no corpo, fazer qualquer inicialização necessária.

public Foo ()

Esse é o construtor padrão para uma classe e, em classes fornecidas pelo Xamarin.iOS, isso inicializa a classe Foundation.NSObject e todas as classes entre elas e, no final, encadeia isso ao Objective-Cinit método na classe .

public Foo (NSObjectFlag x)

Esse construtor é usado para inicializar a instância, mas impede que o código chame o Objective-C método "init" no final. Normalmente, você usa isso quando já se registrou para inicialização (quando usa [Export] em seu construtor) ou quando já fez sua inicialização por meio de outra média.

public Foo (NSCoder coder)

Esse construtor é fornecido para os casos em que o objeto está sendo inicializado de uma instância NSCoding.

Exceções

O design da API do Xamarin.iOS não gera Objective-C exceções como exceções de C#. O design impõe que nenhum lixo seja enviado ao Objective-C mundo em primeiro lugar e que todas as exceções que devem ser produzidas sejam produzidas pela própria associação antes que dados inválidos sejam passados para o Objective-C mundo.

Notificações

No iOS e no OS X, os desenvolvedores podem assinar notificações que são transmitidas pela plataforma subjacente. Isso é feito usando o NSNotificationCenter.DefaultCenter.AddObserver método . O AddObserver método usa dois parâmetros; um é a notificação que você deseja assinar; o outro é o método a ser invocado quando a notificação é gerada.

No Xamarin.iOS e no Xamarin.Mac, as chaves para as várias notificações são hospedadas na classe que dispara as notificações. Por exemplo, as notificações geradas pelo UIMenuController são hospedadas como static NSString propriedades nas UIMenuController classes que terminam com o nome "Notificação".

Gerenciamento de memória

O Xamarin.iOS tem um coletor de lixo que cuidará da liberação de recursos para você quando eles não estiverem mais em uso. Além do coletor de lixo, todos os objetos derivados de NSObject implementam a System.IDisposable interface .

NSObject e IDisposable

Expor a IDisposable interface é uma maneira conveniente de ajudar os desenvolvedores a liberar objetos que podem encapsular grandes blocos de memória (por exemplo, um UIImage pode parecer apenas um ponteiro inocente, mas pode estar apontando para uma imagem de 2 megabytes) e outros recursos importantes e finitos (como um buffer de decodificação de vídeo).

O NSObject implementa a interface IDisposable e também o padrão de descarte do .NET. Isso permite que os desenvolvedores que subclasse NSObject substituam o comportamento dispose e liberem seus próprios recursos sob demanda. Por exemplo, considere esse controlador de exibição que mantém em torno de um monte de imagens:

class MenuViewController : UIViewController {
    UIImage breakfast, lunch, dinner;
    [...]
    public override void Dispose (bool disposing)
    {
        if (disposing){
             if (breakfast != null) breakfast.Dispose (); breakfast = null;
             if (lunch != null) lunch.Dispose (); lunch = null;
             if (dinner != null) dinner.Dispose (); dinner = null;
        }
        base.Dispose (disposing)
    }
}

Quando um objeto gerenciado é descartado, ele não é mais útil. Você ainda pode ter uma referência aos objetos , mas o objeto é para todas as intenções e finalidades inválidas neste ponto. Algumas APIs do .NET garantem isso lançando uma ObjectDisposedException se você tentar acessar quaisquer métodos em um objeto descartado, por exemplo:

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

Mesmo que você ainda possa acessar a variável "image", ela é realmente uma referência inválida e não aponta mais para o Objective-C objeto que mantinha a imagem.

Mas descartar um objeto em C# não significa que o objeto será necessariamente destruído. Tudo o que você faz é liberar a referência que o C# tinha para o objeto . É possível que o ambiente cocoa possa ter mantido uma referência em torno de para seu próprio uso. Por exemplo, se você definir a propriedade Image de um UIImageView como uma imagem e, em seguida, descartar a imagem, a UIImageView subjacente terá feito sua própria referência e manterá uma referência a esse objeto até que ela seja concluída.

Quando chamar Dispose

Chame Dispose quando precisar do Mono para se livrar do objeto. Um possível caso de uso é quando o Mono não tem conhecimento de que seu NSObject está realmente mantendo uma referência a um recurso importante, como memória ou um pool de informações. Nesses casos, você deve chamar Dispose para liberar imediatamente a referência à memória, em vez de esperar que o Mono execute um ciclo de coleta de lixo.

Internamente, quando o Mono cria referências NSString de cadeias de caracteres C#, ele as descarta imediatamente para reduzir a quantidade de trabalho que o coletor de lixo precisa fazer. Quanto menos objetos forem resolvidos, mais rápido o GC será executado.

Quando manter referências a objetos

Um efeito colateral que o gerenciamento automático de memória tem é que o GC se livrará de objetos não utilizados, desde que não haja referências a eles. O que às vezes pode ter efeitos colaterais surpreendentes, por exemplo, se você criar uma variável local para manter o controlador de exibição de nível superior ou a janela de nível superior e, em seguida, tê-los desaparecer pelas costas.

Se você não mantiver uma referência em suas variáveis estáticas ou de instância para seus objetos, Mono chamará alegremente o método Dispose() neles e eles liberarão a referência ao objeto . Como essa pode ser a única referência pendente, o Objective-C runtime destruirá o objeto para você.