ぴーさんログ

だいたいXamarin.Formsのブログ

Xamarin.iOSのメモリ管理

2つのメモリ管理

Xamarin.iOS(.NET 6も同様)では、C#(.NET)側とiOS側(Objective-C, Swift)でそれぞれ独立したメモリ管理をしています。

C#(.NET)では、ガベージコレクタ(GC)を採用しています。 コード上ではメモリ管理を行わず、GC不定期にどこからも参照されていないインスタンスを検査してメモリを解放します。

一方、iOS(Objective-C, Swift)では参照カウンタを採用しています。 歴史的にMRC(Manual Reference Counting)とARC(Automatic Reference Counting)があり、ARCの方が新しい方式です。MRCではコードでメモリ管理を行い、ARCではコンパイラがメモリ管理のコードを自動生成してくれるというイメージです。

Xamarin.iOSでは内部的にMCRを利用してiOS側オブジェクトの参照カウンタ操作を行います。

インスタンス生成~解放

NSObjectなどのiOSの世界のクラスをC#から生成して操作することが出来ますが、この時C#側のインスタンスiOS側のインスタンスがペアで生成されています。

NSObjectクラスを例にして単純なケースを考えると次のような感じです。

C#側からのインスタンス生成

  • C#new NSObject() 実行したとき、このコンストラクタ内部でObjective-C[[NSObject alloc] init] がP/Invoke経由で実行されます。
    • (参照カウンタ 0 → 1)
  • C#側のNSObjectは、生成されたiOS側NSObjectのポインタをHandleプロパティに格納します。

C#側からのインスタンス解放

  • C#側NSObjectが何処からも参照されなくなるとGCに回収され、後始末としてデストラクタが呼ばれる。
  • デストラクタから呼ばれたDisposeがP/Invoke経由で [NSObject release] を実行する。
    • (参照カウンタ N → N-1、この時1 → 0ならiOS側でもメモリ解放)

まとめ

実際にはもちろん、先に作られたiOSインスタンスを元にC#インスタンスが作られるケースもありますし、monoランタイムに絡む制御も複雑で実際に起こる事をトレースしていくのは至難の技です。 (私はそこまで出来てません)

そこまで行かなくとも、C#側とiOS側のメモリ管理とその連携がイメージできると、メモリリーク対策やバグフィックスに役立つと思います。

オマケ

実際の処理が気になる人はxamarin-maciosリポジトリNSObject2.cs を起点に色々見てみましょう。

個人的には、iOS側から(強制的に?)ネイティブオブジェクトを解放されてしまうと検知出来ないのを、Associated Objectを使ってフックして解決してる所なんか面白かったです。 (アタッチ先と一緒に死ぬのでdeinitに処理を仕込めば引っかけられる)

読んでいたソース