Trabalhando com tipos nativos em aplicativos multiplataforma

Este artigo aborda o uso dos novos tipos nativos da API unificada do iOS (nint, nuint, nfloat) em um aplicativo de plataforma cruzada em que o código é compartilhado com dispositivos não iOS, como sistemas operacionais Android ou Windows Phone.

Os tipos nativos de 64 tipos funcionam com as APIs do iOS e do Mac. Se você estiver escrevendo código compartilhado que é executado no Android ou no Windows também, precisará gerenciar a conversão de tipos unificados em tipos .NET regulares que você pode compartilhar.

Este documento discute diferentes maneiras de interoperar com a API unificada a partir de seu código compartilhado/comum.

Quando usar os tipos nativos

As APIs unificadas do Xamarin.iOS e do Xamarin.Mac ainda incluem os inttipos de dados , uint e float , bem como os RectangleFtipos , SizeF e PointF . Esses tipos de dados existentes devem continuar a ser usados em qualquer código compartilhado entre plataformas. Os novos tipos de dados nativos só devem ser usados ao fazer uma chamada para uma API do Mac ou iOS em que o suporte para tipos com reconhecimento de arquitetura é necessário.

Dependendo da natureza do código que está sendo compartilhado, pode haver momentos em que o código entre plataformas pode precisar lidar com os ninttipos de dados e nfloatnuint , e . Por exemplo: uma biblioteca que lida com transformações em dados retangulares que estava sendo usada System.Drawing.RectangleF anteriormente para compartilhar funcionalidades entre as versões Xamarin.iOS e Xamarin.Android de um aplicativo, precisaria ser atualizada para lidar com tipos nativos no iOS.

A forma como essas alterações são tratadas depende do tamanho e da complexidade do aplicativo e da forma de compartilhamento de código que foi usada, como veremos nas seções a seguir.

Considerações sobre compartilhamento de código

Conforme indicado no documento Opções de Código de Compartilhamento, há duas maneiras principais de compartilhar código entre projetos de plataforma cruzada: Projetos Compartilhados e Bibliotecas de Classes Portáteis. Qual dos dois tipos foi usado, limitará as opções que temos ao lidar com os tipos de dados nativos em código de plataforma cruzada.

Projetos de Biblioteca de Classes Portátil

Uma Biblioteca de Classes Portátil (PCL) permite que você direcione as plataformas que deseja oferecer suporte e use interfaces para fornecer funcionalidade específica da plataforma.

Como o tipo de projeto PCL é compilado em um .DLL e não tem noção da API unificada, você será forçado a continuar usando os tipos de dados existentes (int, uint, float) no código-fonte da PCL e o tipo converter as chamadas para as classes e métodos da PCL nos aplicativos front-end. Por exemplo:

using NativePCL;
...

CGRect rect = new CGRect (0, 0, 200, 200);
Console.WriteLine ("Rectangle Area: {0}", Transformations.CalculateArea ((RectangleF)rect));

Projetos compartilhados

O tipo Projeto de Ativo Compartilhado permite que você organize seu código-fonte em um projeto separado que é incluído e compilado nos aplicativos front-end específicos da plataforma individuais e use #if diretivas de compilador conforme necessário para gerenciar requisitos específicos da plataforma.

O tamanho e a complexidade dos aplicativos móveis front-end que estão consumindo código compartilhado, juntamente com o tamanho e a complexidade do código que está sendo compartilhado, precisam ser levados em conta ao escolher o método de suporte para tipos de dados nativos em um projeto de ativo compartilhado entre plataformas.

Com base nesses fatores, os seguintes tipos de soluções podem ser implementados usando as if __UNIFIED__ ... #endif diretivas do compilador para manipular as alterações específicas da API unificada no código.

Usando métodos duplicados

Veja o exemplo de uma biblioteca que está fazendo transformações em dados retangulares fornecidos acima. Se a biblioteca contiver apenas um ou dois métodos muito simples, você poderá optar por criar versões duplicadas desses métodos para Xamarin.iOS e Xamarin.Android. Por exemplo:

using System;
using System.Drawing;

#if __UNIFIED__
using CoreGraphics;
#endif

namespace NativeShared
{
    public class Transformations
    {
        #region Constructors
        public Transformations ()
        {
        }
        #endregion

        #region Public Methods
        #if __UNIFIED__
            public static nfloat CalculateArea(CGRect rect) {

                // Calculate area...
                return (rect.Width * rect.Height);

            }
        #else
            public static float CalculateArea(RectangleF rect) {

                // Calculate area...
                return (rect.Width * rect.Height);

            }
        #endif
        #endregion
    }
}

No código acima, como a CalculateArea rotina é muito simples, usamos a compilação condicional e criamos uma versão separada da API unificada do método. Por outro lado, se a biblioteca contivesse muitas rotinas ou várias rotinas complexas, essa solução não seria viável, pois apresentaria um problema em manter todos os métodos em sincronia para modificações ou correções de bugs.

Usando sobrecargas de método

Nesse caso, a solução pode ser criar uma versão de sobrecarga dos métodos usando tipos de dados de 32 bits para que eles agora tomem CGRect como parâmetro e/ou um valor de retorno, convertam esse valor em um RectangleF (sabendo que a conversão de nfloat para float é uma conversão com perdas) e chamem a versão original da rotina para fazer o trabalho real. Por exemplo:

using System;
using System.Drawing;

#if __UNIFIED__
using CoreGraphics;
#endif

namespace NativeShared
{
    public class Transformations
    {
        #region Constructors
        public Transformations ()
        {
        }
        #endregion

        #region Public Methods
        #if __UNIFIED__
            public static nfloat CalculateArea(CGRect rect) {

                // Call original routine to calculate area
                return (nfloat)CalculateArea((RectangleF)rect);

            }
        #endif

        public static float CalculateArea(RectangleF rect) {

            // Calculate area...
            return (rect.Width * rect.Height);

        }

        #endregion
    }
}

Novamente, essa é uma boa solução, desde que a perda de precisão não afete os resultados para as necessidades específicas do seu aplicativo.

Usando diretivas de alias

Para áreas em que a perda de precisão é um problema, outra solução possível é usar using diretivas para criar um alias para tipos de dados Native e CoreGraphics, incluindo o seguinte código na parte superior dos arquivos de código-fonte compartilhados e convertendo quaisquer valores necessários uintintpara nint, nuint ou nfloatfloat :

#if __UNIFIED__
    // Mappings Unified CoreGraphic classes to MonoTouch classes
    using RectangleF = global::CoreGraphics.CGRect;
    using SizeF = global::CoreGraphics.CGSize;
    using PointF = global::CoreGraphics.CGPoint;
#else
    // Mappings Unified types to MonoTouch types
    using nfloat = global::System.Single;
    using nint = global::System.Int32;
    using nuint = global::System.UInt32;
#endif

Para que nosso código de exemplo se torne:

using System;
using System.Drawing;

#if __UNIFIED__
    // Map Unified CoreGraphic classes to MonoTouch classes
    using RectangleF = global::CoreGraphics.CGRect;
    using SizeF = global::CoreGraphics.CGSize;
    using PointF = global::CoreGraphics.CGPoint;
#else
    // Map Unified types to MonoTouch types
    using nfloat = global::System.Single;
    using nint = global::System.Int32;
    using nuint = global::System.UInt32;
#endif

namespace NativeShared
{

    public class Transformations
    {
        #region Constructors
        public Transformations ()
        {
        }
        #endregion

        #region Public Methods
        public static nfloat CalculateArea(RectangleF rect) {

            // Calculate area...
            return (rect.Width * rect.Height);

        }
        #endregion
    }
}

Note que aqui nós mudamos o CalculateArea método para retornar um nfloat em vez do padrão float. Isso foi feito para que não obtivéssemos um erro de compilação tentando converter implicitamente o nfloat resultado do nosso cálculo (já que ambos os valores que estão sendo multiplicados são do tipo nfloat) em um float valor de retorno.

Se o código for compilado e executado em um dispositivo de API não unificado, o mapeia using nfloat = global::System.Single; o nfloat para um Single que será convertido implicitamente em um float permitindo que o aplicativo front-end consumidor chame o CalculateArea método sem modificação.

Usando conversões de tipo no aplicativo front-end

No caso de seus aplicativos front-end fazerem apenas algumas chamadas para sua biblioteca de código compartilhado, outra solução pode ser deixar a biblioteca inalterada e fazer a transmissão de tipos no aplicativo Xamarin.iOS ou Xamarin.Mac ao chamar a rotina existente. Por exemplo:

using NativeShared;
...

CGRect rect = new CGRect (0, 0, 200, 200);
Console.WriteLine ("Rectangle Area: {0}", Transformations.CalculateArea ((RectangleF)rect));

Se o aplicativo de consumo fizer centenas de chamadas para a biblioteca de código compartilhado, isso novamente pode não ser uma boa solução.

Com base na arquitetura de nosso aplicativo, podemos acabar usando uma ou mais das soluções acima para oferecer suporte a tipos de dados nativos (quando necessário) em nosso código de plataforma cruzada.

Aplicativos Xamarin.Forms

O seguinte é necessário para usar o Xamarin.Forms para interfaces do usuário de plataforma cruzada que também serão compartilhadas com um aplicativo de API unificada:

  • A solução inteira deve estar usando a versão 1.3.1 (ou superior) do Pacote NuGet Xamarin.Forms.
  • Para qualquer renderização personalizada do Xamarin.iOS, use os mesmos tipos de soluções apresentados acima com base em como o código da interface do usuário foi compartilhado (Projeto compartilhado ou PCL).

Como em um aplicativo de plataforma cruzada padrão, os tipos de dados de 32 bits existentes devem ser usados em qualquer código compartilhado entre plataformas para a maioria das situações. Os novos tipos de dados nativos só devem ser usados ao fazer uma chamada para uma API do Mac ou iOS em que o suporte para tipos com reconhecimento de arquitetura é necessário.

Para obter mais detalhes, consulte nossa documentação Atualizando aplicativos Xamarin.Forms existentes.

Resumo

Neste artigo, vimos quando usar os tipos de dados nativos em um aplicativo de API unificada e suas implicações entre plataformas. Apresentamos várias soluções que podem ser usadas em situações em que os novos tipos de dados nativos devem ser usados em bibliotecas multiplataforma. Além disso, vimos um guia rápido para oferecer suporte a APIs unificadas em aplicativos de plataforma cruzada Xamarin.Forms.