クロスプラットフォーム アプリでのネイティブ型の使用

この記事では、コードを Android や Windows Phone OS などの iOS 以外のデバイスと共有するクロスプラットフォーム アプリケーションで、新しい iOS Unified API ネイティブ型 (nint、nuint、nfloat) を使用する場合について説明します。

64 型のネイティブ型は、iOS と Mac の API で動作します。 Android や Windows でも実行される共有コードを記述する場合は、Unified 型から、共有できる通常の .NET 型への変換を実現する必要があります。

このドキュメントでは、共有/共通コードから Unified API と相互運用する、さまざまな方法について説明します。

ネイティブ型を使用するタイミング

Xamarin.iOS および Xamarin.Mac の Unified API には、引き続き、データ型 intuintfloat、および RectangleFSizeFPointF 型が含まれています。 これらの既存のデータ型は、共有されるクロスプラットフォーム コードで引き続き使用されます。 新しいネイティブ データ型は、アーキテクチャ対応型のサポートが必要な Mac または iOS の API を呼び出すときにのみ使用します。

共有されるコードの性質によっては、クロスプラットフォーム コードで、nintnuintnfloat データ型の処理が必要になる場合があります。 たとえば、以前に System.Drawing.RectangleF を使用して Xamarin.iOS と Xamarin.Android バージョンのアプリ間で機能を共有していた、四角形データでの変換を処理するライブラリは、iOS 上のネイティブ型を処理するように更新する必要があります。

これらの変更の処理方法は、この後のセクションで説明するように、アプリケーションのサイズと複雑さ、および使用されているコード共有の形式によって異なります。

コード共有に関する考慮事項

コード共有オプションに関するドキュメントに記載されているように、クロスプラットフォーム プロジェクト間でコードを共有する主な方法として、共有プロジェクトおよび移植可能クラス ライブラリの 2 つがあります。 2 つの種類のうち、どちらが使用されているかによって、クロスプラットフォーム コードでネイティブ データ型を処理する際のオプションが制限されます。

移植可能クラス ライブラリ プロジェクト

移植可能クラス ライブラリ (PCL) を使用すると、サポートする必要があるプラットフォームをターゲットとし、インターフェイスを使用してプラットフォーム固有の機能を提供できます。

PCL プロジェクトという種類は .DLL にコンパイルされ、そこには Unified API の認識がないため、PCL ソース コードで既存のデータ型 (intuintfloat) を使用し続け、フロントエンド アプリケーションで PCL のクラスとメソッドの呼び出しを型キャストする必要があります。 次に例を示します。

using NativePCL;
...

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

共有プロジェクト

共有アセット プロジェクトという種類を使用すると、ソース コードを個別のプロジェクトに整理して、個々のプラットフォーム固有のフロントエンド アプリに組み込んでコンパイルでき、必要に応じて #if コンパイラ ディレクティブを使用してプラットフォーム固有の要件に対応できます。

クロスプラットフォーム共有アセット プロジェクトでのネイティブ データ型のサポート方法を選択する際には、共有コードを使用するフロントエンド モバイル アプリケーションのサイズと複雑さ、および共有されるコードのサイズと複雑さを考慮する必要があります。

これらの要因に基づいて、コードに対する Unified API 固有の変更を処理するために、if __UNIFIED__ ... #endif コンパイラ ディレクティブを使用して次の種類のソリューションを実装できます。

重複メソッドを使用する

上記に示した四角形データでの変換を行うライブラリの例を取り上げます。 このライブラリに非常に単純なメソッドが 1 つまたは 2 つだけ含まれている場合は、Xamarin.iOS と Xamarin.Android 用にこれらのメソッドの重複バージョンを作成することを選択できます。 次に例を示します。

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

上記のコードでは、CalculateArea ルーチンは非常に単純であるため、条件付きコンパイルを使用し、別個の Unified API バージョンのメソッドを作成しました。 それに対し、ライブラリに多数のルーチンまたは複数の複雑なルーチンが含まれている場合、この解決策は実現できません。変更やバグの修正プログラムのためにすべてのメソッドの同期を維持するという問題が発生するからです。

メソッドのオーバーロードを使用する

その場合、考えられる解決策は、32 ビット データ型を使用してメソッドのオーバーロード バージョンを作成して、CGRect をパラメーターまたは戻り値として受け取るようにし、(nfloat から float への変換が損失を伴う変換であることを認識したうえで) その値を RectangleF に変換し、ルーチンの元のバージョンを呼び出して実際の処理を行うことです。 次に例を示します。

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

この場合も、精度の損失がアプリケーション固有のニーズに応じた結果に影響しない限り、これは適切な解決策です。

別名ディレクティブを使用する

精度の損失が問題になる分野の場合、考えられる別の解決策として、using ディレクティブを使用してネイティブおよび CoreGraphics データ型の別名を作成します。そのために、次のコードを共有ソース コード ファイルの先頭に含め、必要な intuint、または float 値を nintnuint、または nfloat に変換します。

#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

そうすると、コード例は次のようになります。

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

ここでは、標準の float の代わりに nfloat を返すように CalculateArea メソッド変更しました。 これを行ったのは、計算の nfloat 結果を "暗黙的に" float 戻り値に変換しようとして生じるコンパイル エラーが起こらないようにするためです (乗算される両方の値が nfloat 型であるため)。

コードがコンパイルされ、Unified API 以外のデバイスで実行される場合、using nfloat = global::System.Single; では、nfloatSingle に変換しますが、これは、暗黙的に float に変換され、使用側のフロントエンド アプリケーションで変更を行わずに CalculateArea メソッドを呼び出すことができるようにします。

フロントエンド アプリで型変換を使用する

フロントエンド アプリケーションから共有コード ライブラリへの呼び出し回数がわずかしかない場合、別の解決策として、ライブラリを変更しないままにし、既存のルーチンを呼び出すときに Xamarin.iOS または Xamarin.Mac アプリケーションで型キャストを行うことができます。 次に例を示します。

using NativeShared;
...

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

使用側のアプリケーションから共有コード ライブラリへの呼び出し回数が何百回にものぼる場合は、やはり適切な解決策ではありません。

アプリケーションのアーキテクチャに基づいて、最終的には、上記の解決策の 1 つ以上を使用して、クロスプラットフォーム コードで (必要に応じて) ネイティブ データ型をサポートするようにします。

Xamarin.Forms アプリケーション

Unified API アプリケーションとも共有されるクロスプラットフォーム UI に Xamarin.Forms を使用するには、以下が必要です。

  • ソリューション全体で Xamarin.Forms NuGet パッケージのバージョン 1.3.1 (以上) を使用している必要があります。
  • Xamarin.iOS カスタム レンダリングの場合は、UI コードがどのように共有されているか (共有プロジェクトまたは PCL) に基づいて、上記に示したものと同じ種類の解決策を使用します。

標準のクロスプラットフォーム アプリケーションと同様に、ほとんどの状況で、共有されているクロスプラットフォーム コードに既存の 32 ビット データ型を使用する必要があります。 新しいネイティブ データ型は、アーキテクチャ対応型のサポートが必要な Mac または iOS の API を呼び出すときにのみ使用します。

詳細については、ドキュメント「既存の Xamarin.Forms アプリの更新」を参照してください。

まとめ

この記事では、どのようなときに ネイティブ データ型を Unified API アプリケーションで使用するか、およびそれがクロスプラットフォームで持つ意味合いについて確認してきました。 クロスプラットフォーム ライブラリで新しいネイティブ データ型を使用する必要がある状況で使用できる、いくつかの解決策を紹介しました。 また、Xamarin.Forms クロスプラットフォーム アプリケーションでの Unified API のサポートに関するクイック ガイドも紹介しました。