Xamarin.iOS API の設計

Xamarin.iOS には、Mono に含まれる中心的基底クラス ライブラリに加え、開発者が Mono でネイティブ iOS アプリケーションを作成するためのさまざまな iOS API 向けバインディングが付属しています。

Xamarin.iOS の中心となるのは、C# の領域と Objective-C の領域の橋渡しとなる相互運用エンジンと、CoreGraphics や OpenGL ES のような、iOS C ベースの API 向けのバインディングです。

Objective-C コードと通信するための低レベルのランタイムが MonoTouch.ObjCRuntime です。 さらに、Foundation、CoreFoundation、UIKit 向けのバインディングが提供されます。

設計原則

このセクションでは、Xamarin.iOS バインディングの設計原則のいくつかについて詳しく説明します (これらは、macOS の Objective-C 用 Mono バインディングである Xamarin.Mac にも適用されます)。

  • フレームワーク デザインのガイドラインに従う

  • Objective-C クラスのサブクラス化を開発者に許可する

    • 既存クラスから派生させる
    • 連鎖する基本コンストラクターを呼び出す
    • オーバーライド メソッドは C# のオーバーライド システムで行う
    • サブクラス化は C# 標準コンストラクトで動作しなければならない
  • 開発者を Objective-C セレクターに触れさせない

  • 任意の Objective-C ライブラリを呼び出すメカニズムを与える

  • 一般的な Objective-C タスクを簡単にし、難しい Objective-C タスクを可能にする

  • Objective-C プロパティを C# プロパティとして公開する

  • 厳密に型指定された API を公開する

    • タイプ セーフ (型の安全性) の程度を大きくする
    • ランタイム エラーを最小限に抑える
    • 戻り値の型で IDE IntelliSense を取得する
    • IDE ポップアップ ドキュメントの準備をする
  • API の IDE 内でいろいろ試すことを奨励する

    • たとえば、次のような弱く型指定された配列を公開するのではなく

      NSArray *getViews
      

      このように厳密な型を公開します

      NSView [] Views { get; set; }
      

      厳密な型指定を使用すると、Visual Studio for Mac で API を閲覧中に、オートコンプリート機能を利用できます。また、戻り値であらゆる System.Array 操作が可能になり、LINQ に加わることが戻り値に許可されます。

  • ネイティブ C# 型:

    • NSStringstring になります。

    • 列挙型にした方が良い int パラメーターと uint パラメーターを C# 列挙型と [Flags] 属性を持つ C# 列挙型に変えます

    • 型に依存しない NSArray ではなく、厳密に型指定された配列として配列を公開します。

    • イベントと通知については、ユーザーに次から選択してもらいます。

      • 既定で、厳密に型指定されたバージョン
      • 上級ユース ケースのための弱く型指定されたバージョン
  • Objective-C デリゲート パターンのサポート:

    • C# イベント システム
    • C# デリゲート (ラムダ、匿名メソッド、System.Delegate) を Objective-C API にブロックとして公開します

アセンブリ

Xamarin.iOS には、Xamarin.iOS Profile を構成する多くのアセンブリが含まれています。 詳細については、アセンブリに関するページを参照してください。

主要な名前空間

ObjCRuntime

ObjCRuntime 名前空間で開発者は C# の領域と Objective-C の領域をつなぐことができます。 これは、Cocoa# と Gtk# での経験に基づき、iOS のために特別に設計された新しいバインディングです。

財団

Foundation 名前空間には、iOS に含まれる Objective-C Foundation フレームワークとの相互運用のために設計された基本的なデータ型が用意されています。また、これは Objective-C のオブジェクト指向プログラミングの基本です。

Xamarin.iOS によって C# で Objective-C のクラス階層が忠実に再現されます。 たとえば、Objective-C 基底クラス NSObject は Foundation.NSObject 経由で C# から利用できます。

Foundation 名前空間からは基礎となる Objective-C Foundation 型のバインディングが提供されますが、基礎となる型を .NET 型にマッピングしたケースもありました。 次に例を示します。

  • NSString と NSArray を扱う代わりに、このランタイムでは、API 全体でこの 2 つがそれぞれ C# string と厳密に型指定された array として公開されます。

  • 開発者がサードパーティの Objective-C API、その他の iOS API、または Xamarin.iOS で現在バインディングされていない API をバインディングできるよう、さまざまなヘルパー API がここで公開されます。

API のバインディングに関する詳細については、Xamarin.iOS のバインド生成機能に関するセクションを参照してください。

NSObject

NSObject 型は、すべての Objective-C バインドの基礎となります。 Xamarin.iOS 型は、iOS CocoaTouch API の 2 つの型クラス、C 型 (通常、CoreFoundation 型と呼ばれる) と Objective-C 型 (すべて NSObject クラスから派生) を忠実に再現するものです。

アンマネージド型を忠実に再現する型については、Handle プロパティからネイティブ オブジェクトを与えることができます。

Mono では、あらゆるオブジェクトがガベージ コレクションされますが、Foundation.NSObject では、System.IDisposable インターフェイスが実装されます。 ガベージ コレクターの起動を待つ必要なく、任意の NSObject のリソースを明示的に解放できます。 リソースを明示的に解放することは、重い NSObject (たとえば、大きなデータ ブロックをポイントしている UIImage) を使用しているときに重要です。

自分で定義した型で確実にファイナライズを行う必要がある場合、NSObject.Dispose(bool) メソッドをオーバーライドします。Dispose のパラメーターは "bool disposing" です。true に設定されている場合、Dispose メソッドが呼び出されています。ユーザーがオブジェクトで Dispose () を明示的に呼び出したからです。 false の値は、ファイナライザー スレッドのファイナライザーから Dispose(bool disposing) メソッドが呼び出されていることを意味します。

カテゴリー

Xamarin.iOS 8.10 以降では、C# から Objective-C カテゴリを作成できます。

これは Category 属性を使用して行います。属性の引数として適用範囲を拡張する型を指定します。 たとえば、次の例では、NSString の適用範囲が拡張されます。

[Category (typeof (NSString))]

各カテゴリ メソッドで、Export 属性で Objective-C にメソッドをエクスポートする通常のメカニズムが使用されています。

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

マネージド拡張メソッドはすべて静的にする必要がありますが、C# の拡張メソッドの標準構文を使用し、Objective-C インスタンス メソッドを作成できます。

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

また、拡張メソッドの最初の引数は、メソッドが呼び出されたインスタンスになります。

コード例全体:

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

この例では、Objective-C から呼び出せる NSString クラスにネイティブ toUpper インスタンス メソッドが追加されます。

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

これが役に立つシナリオの 1 つは、コードベースのクラス セット全体にメソッドを追加することです。たとえば、これによりすべての UIViewController インスタンスでそれがローテーションできることが報告されます。

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

PreserveAttribute は、Xamarin.iOS 展開ツールである mtouch に、アプリケーションがそのサイズを減らすように処理されているとき、型または型のメンバーを保存するように指示する目的で使用されるカスタム属性です。

アプリケーションで静的にリンクされないメンバーはすべて、削除の対象となります。 そのため、この属性は、静的に参照されないが、アプリケーションで必要となるメンバーに印を付ける目的で使用されます。

たとえば、型を動的にインスタンス化する場合、型の既定のコンストラクターを保存することもできます。 XML シリアル化を利用する場合は、型のプロパティを保持することができます。

この属性は、ある型のすべてのメンバーに適用するか、型自体に適用できます。 型全体を保持する場合、その型で構文 [Preserve (AllMembers = true)] を利用できます。

UIKit

UIKit 名前空間には、C# クラス形式で CocoaTouch を構成するあらゆる UI コンポーネントに対する一対一のマッピングが含まれます。 API は、C# 言語で使用されている規則に従うように変更されています。

C# デリゲートが一般的な操作のために用意されています。 詳細については、「デリゲート」セクションを参照してください。

OpenGLES

OpenGLES については、OpenTK API の改良版を配布しています。この API は、CoreGraphics のデータ型と構造体を使用するように変更されている OpenGL にオブジェクト指向でバインディングするものであり、また、iOS で利用できる機能のみが公開されます。

OpenGLES 1.1 機能は ES11.GL 型から利用できます。

OpenGLES 2.0 機能は ES20.GL 型から利用できます。

OpenGLES 3.0 機能は ES30.GL 型から利用できます。

バインディング デザイン

Xamarin.iOS では、基礎となる Objective-C プラットフォームにバインディングするだけではありません。 これにより .NET 型システムとディスパッチ システムが拡張され、C# と Objective-C が一層効果的に融合されます。

Windows と Linux でネイティブ ライブラリを呼び出すツールとして P/Invoke が便利なように、あるいは Windows での COM 相互運用に IJW サポートを使用できるように、Xamarin.iOS によって、C# オブジェクトと Objective-C オブジェクトをバインドできるようにランタイムが拡張されます。

後続のセクションで説明する内容は、Xamarin.iOS アプリケーションを作成するユーザーにとって必要ではありませんが、開発者がしくみを理解したり、もっと複雑なアプリケーションを作成したりするときに役立ちます。

道理にかなうのなら、下位の Foundation 型ではなく C# 型が C# の領域に公開されます。 つまり、API では NSString ではなく C# の sring 型が使用され、NSArray を公開せず、厳密に型指定された C# 配列が使用されます。

一般に、Xamarin.iOS と Xamarin.Mac の設計では、基礎になる NSArray オブジェクトは公開されません。 代わりに、ランタイムによって NSObject クラスの厳密に型指定された配列に NSArray が自動的に変換されます。 そのため、Xamarin.iOS により、NSArray を返す目的で GetViews のような弱く型指定されたメソッドが公開されることはありません。

NSArray GetViews ();

代わりに、次のように、バインディングによって、厳密に型指定された戻り値が公開されます。

UIView [] GetViews ();

NSArray を直接使用するような不健全な状況では NSArray でいくつかのメソッドが公開されることがありますが、API バインディングではその使用は推奨されていません。

また、Classic API では、CoreGraphics API から CGRectCGPointCGSize を公開せず、System.Drawing の実装の RectangleFPointFSizeF に代えました。OpenTK を使用する既存の OpenGL コードを開発者が残せるからです。 新しい 64 ビット Unified API を使用する場合、CoreGraphics API を使用してください。

継承

Xamarin.iOS API デザインでは、派生クラスにキーワード "override" を使用し、また、C# キーワード "base" を使用して基礎実装に連結することで、C# 型を拡張する場合と同様に、開発者はネイティブ Objective-C 型を拡張できます。

このデザインでは、開発者は開発プロセスの一環として Objective-C セレクターを扱わずに済みます。Objective-C システム全体が既に Xamarin.iOS ライブラリ内でラップされているためです。

型と Interface Builder

作成した .NET クラスが Interface Builder で作成された型のインスタンスになるとき、IntPtr パラメーターを 1 つ受け取るコンストラクターを指定する必要があります。 これは、マネージド オブジェクト インスタンスをアンマネージド オブジェクトにバインドするために必要です。 コードは次のような 1 行で構成されます。

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

デリゲート

Objective-C と C# では、それぞれの言語においてデリゲートという言葉の意味が異なります。

Objective-C の世界と、CocoaTouch に関してネットで見つかる文書では、デリゲートは通常、メソッドのセットに応答するクラスのインスタンスです。 これは C# インターフェイスに似ていますが、メソッドが常に必須になるとは限らないところが異なります。

このようなデリゲートは、UIKit とその他の CocoaTouch API で重要な役割を果たします。 次のようなさまざまな作業をこなすために使用されます。

  • 通知をコードに与える (C# や Gtk+ のイベント配信に似ている)。
  • データ可視化制御のモデルを実装する。
  • 制御動作を動かす。

プログラミング パターンは、制御動作を変更する派生クラスの作成を最小限に抑えるように設計されました。 このソリューションは、Gtk のシグナル、Qt スロット、Winforms イベント、WPF/Silverlight イベントなど、ほかの GUI ツールキットが長年にわたって行ってきたことと概念が似ています。 何百ものインターフェイスを用意する (アクションごとに 1 つのインターフェイス) ことや不要なメソッドを過度に実装することを避けるために、Objective-C では、オプションのメソッド定義がサポートされています。 これは、すべてのメソッドを実装する必要がある C# インターフェイスとは異なります。

Objective-C クラスでは、このプログラミング パターンが使用されるクラスでプロパティが公開されることがわかります。そのプロパティは、delegate という名前ですが、インターフェイスの必須およびオプション部分を実装するために必要です。

Xamarin.iOS からは、これらのデリゲートにバインドする互いに排他的なメカニズムが 3 つ与えられます。

  1. イベント経由
  2. Delegate プロパティ経由で厳密に型指定される
  3. WeakDelegate プロパティ経由で緩やかに型指定される

たとえば、UIWebView クラスについて考えてみます。 これは delegate プロパティに割り当てられる UIWebViewDelegate インスタンスにディスパッチされます。

イベント経由

さまざまな型に対して、Xamarin.iOS によって適切なデリゲートが自動的に作成され、そのデリゲートによって UIWebViewDelegate 呼び出しが C# イベントに転送されます。 UIWebView の場合:

  • webViewDidStartLoad メソッドが UIWebView.LoadStarted イベントにマッピングされます。
  • webViewDidFinishLoad メソッドが UIWebView.LoadFinished イベントにマッピングされます。
  • webView:didFailLoadWithError メソッドが UIWebView.LoadError イベントにマッピングされます。

たとえば、この単純なプログラムでは、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;
プロパティ経由

イベントは、イベントのサブスクライバーが複数存在するような場合に便利です。 また、イベントは、コードからの戻り値がない場合に限られます。

値を返すことがコードに求められる場合、代わりにプロパティを選択しました。 つまり、オブジェクト内の特定の時点にメソッドを 1 つだけ設定できます。

たとえば、このメカニズムを利用し、UITextField のハンドラーでキーボードを画面から消すことができます。

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

この場合の UITextFieldShouldReturn プロパティはブール値を返すデリゲートを引数として受け取り、[Return] ボタンが押されたとき、TextField で何かすべきかどうかを判断します。 今回のメソッドでは、呼び出し元に "true" を返しますが、画面からキーボードを削除することもします (textfield で ResignFirstResponder が呼び出されるとこれが発生します)。

Delegate プロパティ経由で厳密に型指定される

イベントを使用しないことを望む場合、独自の UIWebViewDelegate サブクラスを指定し、それを UIWebView.Delegate プロパティに割り当てることができます。 UIWebView.Delegate が割り当てられると、UIWebView ビューのディスパッチ メカニズムが機能しなくなります。それに対応するイベントが発生すると、UIWebViewDelegate メソッドが呼び出されます。

たとえば、この単純な型では、Web ビューを読み込むために必要な時間が記録されます。

class Notifier : UIWebViewDelegate  {
    DateTime startTime, endTime;

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

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

上の例はこのようなコードで使用されます。

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

上の例では、UIWebViewer が作成され、メッセージに応答するものとして今回作成したクラス、Notifier のインスタンスにメッセージを送信するように UIWebViewer に指示します。

このパターンは、特定のコントロールの動作を制御する目的でも使用されます。たとえば、UIWebView の場合、UIWebView.ShouldStartLoad プロパティによって、UIWebView インスタンスは UIWebView でページを読み込むかどうかを制御できます。

このパターンは、いくつかのコントロールで必要なときにデータを与える目的でも使用されます。 たとえば、UITableView コントロールは強力なテーブルレンダリング コントロールです。外見も中身も UITableViewDataSource のインスタンスによって動きます。

WeakDelegate プロパティ経由の弱い型付け

厳密に型指定されたプロパティに加え、弱く型付けされたデリゲートもあります。弱い型付けをすることで、開発者が必要に応じて違ったやり方でバインドできます。 Xamarin.iOS のバインディングで厳密に型指定された Delegate プロパティが公開されている場合、それに対応する WeakDelegate プロパティも必ず公開されています。

WeakDelegate を使用するとき、Export 属性でクラスを正しく装飾し、セレクターを指定する必要があります。 次に例を示します。

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 ();

WeakDelegate プロパティが割り当てられると、Delegate プロパティは使用されなくなります。 また、継承された基底クラスをエクスポートすることを望んでいるとすれば、その基底クラスでメソッドを実装する場合、それをパブリック メソッドにする必要があります。

Objective-C デリゲート パターンを C# にマッピングする

次のような Objective-C サンプルがあります。

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

このサンプルでは、クラス "SomethingDelegate" のインスタンスを作成して構築し、foo 変数の delegate プロパティに値を割り当てるよう、言語が指示されます。 このメカニズムは Xamarin.iOS と C# でサポートされています。構文は次のようになります。

foo.Delegate = new SomethingDelegate ();

Xamarin.iOS では、Objective-C デリゲート クラスにマッピングされる、厳密に型指定されたクラスを指定しました。 それを使用するために、そのクラスをサブクラス化し、Xamarin.iOS の実装で定義されたメソッドをオーバーライドします。 機能の詳細については、下の「モデル」セクションを参照してください。

デリゲートを C# にマッピングする

一般的な UIKit では、Objective-C デリゲートが 2 つの形式で使用されます。

最初の形式では、コンポーネントのモデルにインターフェイスが与えられます。 たとえば、ビューに必要に応じてデータを提供するメカニズムとしてです。List ビューのデータ ストレージ機能などです。 このような場合、常に適切なクラスのインスタンスを作成し、変数を割り当ててください。

次の例では、文字列を使用するモデルの実装で UIPickerView を指定します。

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 ();

2 番目の形式は、イベントの通知を提供することです。 そのような場合、上記のような形式で API を公開しますが、C# イベントも指定します。それにより、簡単な操作で簡単に使用できます。また、C# で匿名のデリゲートやラムダ式と統合されます。

たとえば、UIAccelerometer イベントをサブスクライブできます。

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

イベントを使用するのに適切な場面であれば 2 つのオプションを利用できますが、プログラマーはいずれかを選択する必要があります。 厳密に型指定されたレスポンダー/デリゲートのインスタンスを自分で作成し、それを割り当てると、C# イベントは機能しません。 C# イベントを使用する場合、レスポンダー/デリゲート クラスのメソッドは決して呼び出されません。

UIWebView を使用した前の例は、次のように C# 3.0 ラムダを利用して記述できます。

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

イベントへの応答

Objective-C コードでは、複数のコントロールのイベント ハンドラーと複数のコントロールの情報プロバイダーが同じクラスでホストされることがあります。 これは、クラスがメッセージに応答するために可能です。また、クラスがメッセージに応答する限り、オブジェクトを連結できます。

前に説明したように、Xamarin.iOS は C# イベントベースのプログラミング モデルに対応しており、かつ、デリゲートを実装し、望むメソッドをオーバーライドする新しいクラスを作成できる Objective-C デリゲート パターンに対応しています。

また、複数の異なる操作のレスポンダーがすべて、あるクラスの同じインスタンスにホストされる Objective-C パターンに対応しています。 ただし、これを行うには、Xamarin.iOS バインディングの下位機能を使用する必要があります。

たとえば、あるクラスの同じインスタンスで UITextFieldDelegate.textFieldShouldClear: メッセージと UIWebViewDelegate.webViewDidStartLoad: の両方に自分のクラスを応答させる場合、[Export] 属性宣言を使用する必要がありました。

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

C# のメソッド名は重要ではありません。重要なのは [Export] 属性に渡される文字列です。

このスタイルのプログラミングを使用するとき、ランタイム エンジンから渡される実際の型と C# プログラミングが一致するようにします。

モデル

UIKit ストレージ機能や、ヘルパー クラスを使用して実装されたレスポンダーでは、Objective-C コードでデリゲートとして参照され、プロトコルとして実装されます。

Objective-C のプロトコルはインターフェイスのようなものですが、定義されているメソッドを実装するかどうかは自由に選択できます。つまり、全部のメソッドを実装しなくてもプロトコルは動作します。

モデルは 2 とおりの方法で実装されます。 手動で実装するか、既存の厳密に型指定された定義を使用できます。

手動メカニズムは、Xamarin.iOS でバインドされていないクラスを実装するときに必要です。 次のように簡単に行うことができます。

  • ランタイムで登録するためのフラグをクラスに付ける
  • オーバーライドするメソッドごとに実際のセレクター名を付け、[Export] 属性を適用する
  • クラスをインスタンス化し、渡す

たとえば、次の場合、UIApplicationDelegate プロトコル定義で任意のメソッドのうち、1 つだけが実装されます。

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

Objective-C セレクター名 ("applicationDidFinishLaunching:") は Export 属性で宣言され、クラスは [Register] 属性で登録されます。

Xamarin.iOS からは厳密に型指定された宣言が与えられます。これはすぐに使用できるもので、手動でバインドする必要がありません。 このプログラミング モデルをサポートするために、Xamarin.iOS ランタイムでは、クラス宣言で [Model] 属性がサポートされます。 これにより、メソッドが明示的に実装されていない限り、クラスのすべてのメソッドを接続するよう、ランタイムに通知されます。

つまり、UIKit では、任意のメソッドを指定したプロトコルを表わすクラスは次のように記述されます。

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

一部のメソッドのみを実装するモデルを実装する場合、必要なことは、関心のあるメソッドをオーバーライドし、その他のメソッドを無視することです。 ランタイムでは、上書きされたメソッドのみが Objective-C の領域に接続され、元のメソッドは接続されません。

前の手動サンプルに相当するものが以下です。

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

この長所は、セレクター、引数の種類、C# へのマッピングを見つける目的で Objective-C ヘッダー ファイルを調べる必要がないことと、Visual Studio for Mac の IntelliSense と厳密な型を使用できることです。

XIB Outlets と C#

これは Outlets が C# と統合するしくみを詳しく説明したものであり、Xamarin.iOS の上級ユーザー向けに提供されています。 Visual Studio for Mac を使用するときは、その場で自動的に生成されたコードを利用し、裏側で自動的にマッピングが行われます。

Interface Builder でユーザー インターフェイスを設計すると、アプリケーションの外見を設計し、既定の接続をいくつか確立するだけになります。 プログラムを利用して情報をフェッチする、実行時のコントロールの動作を変更する、あるいは実行時のコントロールを変更する場合、一部のコントロールをマネージド コードにバインドする必要があります。

これは数回のステップで行われます。

  1. アウトレット宣言ファイルの所有者に追加します。
  2. コントロールをファイルの所有者に接続します。
  3. UI と接続を XIB/NIB ファイルに格納します。
  4. 実行時に NIB ファイルを読み込みます。
  5. アウトレット変数にアクセスします。

手順 (1) から (3) までは Apple の Interface Builder でインターフェイスを構築する方法に関するドキュメントで取り上げられています。

Xamarin.iOS の使用時、UIViewController から派生されるクラスをアプリケーションで作成する必要があります。 これは次のように実装されます。

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

それから、NIB ファイルから ViewController を読み込みために次を行います。

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

これにより、NIB からユーザー インターフェイスが読み込まれます。 次に、アウトレットにアクセスするために、アウトレットにアクセスすることをランタイムに知らせる必要があります。 これを行うために、UIViewController サブクラスでプロパティを宣言し、[Connect] 属性で注釈を付ける必要があります。 例:

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

プロパティ実装は、実際のネイティブ型の値を実際に取得し、格納する実装です。

Visual Studio for Mac と InterfaceBuilder を使用するとき、これに関して心配する必要はありません。 Visual Studio for Mac では、プロジェクトの一部としてコンパイルされた部分的クラスのコードによって、宣言済みのアウトレットがすべて自動的にミラー化されます。

セレクター

Objective-C プログラミングの中心的概念はセレクターです。 セレクターを渡すこと要求する API やセレクターに応答することをコードに求める API には頻繁に遭遇します。

C# では簡単に新しいセレクターを作成できます。ObjCRuntime.Selector クラスの新しいインスタンスを作成し、それを必要とする API の場所で使用するだけです。 次に例を示します。

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

C# メソッドがセレクターの呼び出しに応答するには、それが NSObject 型から派生している必要があり、[Export] 属性を利用し、セレクター名で C# メソッドを修飾する必要があります。 次に例を示します。

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

セレクター名は、中間と末尾にコロン (":") があればそれも含め、厳密に一致する必要があります

NSObject コンストラクター

NSObject から派生した Xamarin.iOS クラスのほとんどでオブジェクトの機能に固有のコンストラクターが公開されますが、すぐにはわからないさまざまなコンストラクターも公開されます。

コンストラクターは次のように使用されます。

public Foo (IntPtr handle)

このコンストラクターは、ランタイムでクラスをアンマネージド クラスにマッピングする必要があるとき、クラスをインスタンス化する目的で使用されます。 これは XIB/NIB ファイルを読み込むときに行われます。 この時点で、Objective-C ランタイムによりアンマネージドの領域でオブジェクトが作成されているでしょう。また、このコンストラクターが呼び出され、マネージド側が初期化されます。

通常、必要な作業は、ハンドル パラメーターで基本コンストラクターを呼び出し、本文で必要な初期化を行うことだけです。

public Foo ()

これはクラスの既定のコンストラクターです。Xamarin.iOS 付属のクラスでは、これにより Foundation.NSObject クラスと間にあるすべてのクラスが初期化され、最後に、クラスの Objective-Cinit メソッドにこれが連結されます。

public Foo (NSObjectFlag x)

このコンストラクターはインスタンスを初期化する目的で使用されますが、コードが最後に Objective-C "init" メソッドを呼び出すことを防ぎます。 初期化に既に登録しているとき (コンストラクターで [Export] を使用するとき)、または、別の手段で初期化を既に完了しているとき、通常、これを使用します。

public Foo (NSCoder coder)

このコンストラクターは、オブジェクトが NSCoding インスタンスから初期化されている場合に与えられます。

例外

Xamarin.iOS API デザインでは、Objective-C 例外が C# 例外として発生しません。 このデザインでは、第一に Objective-C の領域にガベージが送信されません。そして、生成しなければならない例外があれば、無効なデータが Objective-C の領域に渡される前にそれが生成されます。

通知

iOS と OS X の両方で、基礎となるプラットフォームでブロードキャストされる通知を開発者が定期購読できます。 これは NSNotificationCenter.DefaultCenter.AddObserver メソッドを使用して行われます。 AddObserver メソッドは 2 つのパラメーターを受け取ります。1 つは、定期購読する通知です。もう 1 つは、通知が発生したときに呼び出すメソッドです。

Xamarin.iOS と Xamarin.Mac の両方で、さまざまな通知のキーは、通知をトリガーするクラスでホストされます。 たとえば、UIMenuController で生成される通知は、"Notification" という名前で終わる UIMenuController クラスで static NSString プロパティとしてホストされます。

メモリ管理

Xamarin.iOS に含まれるガベージ コレクターによって、不要になったリソースが解放されます。 ガベージ コレクターに加え、NSObject から派生されるオブジェクトはすべて System.IDisposable インターフェイスを実装します。

NSObject と IDisposable

オブジェクトによってカプセル化が行われることがあります。大きなメモリ ブロック (たとえば、UIImage は単純なポインターのように見えますが、2 メガバイトの画像を指していることがあります) や (動画の復号バッファーのような) その他の重要な有限リソースがカプセル化されます。そのようなオブジェクトを解放するとき、開発者を支援する便利な方法が IDisposable インターフェイスの公開です。

NSObject では IDisposable interface が実装され、さらに .NET Dispose パターンも実装されます。 これにより、開発者は NSObject をサブクラス化し、Dispose 動作をオーバーライドして自分だけのリソースを必要なときに解放できます。 たとえば、画像の集まりを維持するこのビュー コントローラーについて考えてください。

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

破棄されたマネージド オブジェクトはもう役に立ちません。 オブジェクトの参照が残っているかもしれませんが、どの点から見ても、破棄後のオブジェクトはこの時点で無効です。 これを確実に行う .NET API もあります。たとえば、次のように、破棄されたオブジェクトでメソッドにアクセスしようとすると、ObjectDisposedException をスローします。

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

引き続き変数 "image" にアクセスできるとしても、それは実際には無効な参照であり、画像を保持していた Objective-C オブジェクトを今はポイントしません。

ただし、C# でオブジェクトを破棄しても、そのオブジェクトは必ずしも完全に削除されていません。 C# で与えられたオブジェクトの参照を解放しただけです。 Cocoa 環境がそこで使用するために参照を維持している可能性があります。 たとえば、UIImageView の Image プロパティをある画像に設定し、その画像を破棄した場合、基礎となる UIImageView では独自の参照を取得しており、このオブジェクトの参照を使い終わるまで維持します。

Dispose を呼び出す状況

Mono でオブジェクトを削除する必要があるときは、Dispose を呼び出します。 考えられるユース ケースは、メモリや情報プールなど、重要なリソースの参照が NSObject で実際に維持されていることが Mono で認識されていないときです。 そのようなケースでは、Mono がガベージ コレクションを実行するのを待たず、Dispose を呼び出し、メモリの参照をすぐに解放してください。

内部的には、Mono によって C# 文字列から NSString 参照が作成されるとき、参照をすぐに破棄し、ガベージ コレクターが関連する作業量を減らします。 扱うオブジェクトの数が少なければ、それだけ GC の実行時間が短くなります。

オブジェクトの参照を維持する状況

自動メモリ管理の副作用の 1 つは、参照がない限り、使用されていないオブジェクトが GC によって削除されることです。 これにより意外な副作用が生じることがあります。たとえば、最上位ビュー コントローラーまたは最上位ウィンドウを維持するローカル変数を作成したのに、それがひっそりと消えます。

静的変数またはインスタンス変数でオブジェクトの参照を維持しないなら、Mono によって変数で Dispose() メソッドがしっかりと実行され、オブジェクトの参照が解放されます。 これが唯一の未処理の参照という場合もあり、Objective-C ランタイムによってオブジェクトが自動的に破棄されます。