メモリ管理

Java にはガベージ コレクションがデフォルトであり、Objective-C にははデフォルトでないため、J2ObjC でメモリ管理がどのように実装されているかという質問が大勢の Java デベロッパーに寄せられます。iOS には、参照カウントと自動参照カウント(ARC)の 2 つの方法があります。

J2ObjC は、選択した方法に応じて異なるメモリ管理コードを生成します。-use-arc オプションを指定して翻訳し、ARC を使用するコードを生成します。デフォルトでは、手動参照カウントが使用されます。

参照カウント

参照カウント方法を使用すると、オブジェクトの所有権を明示的にできます。メソッドはオブジェクトを作成したとき、そのオブジェクトを解放するまでそのオブジェクトを所有します。受信側のメソッドは、別のメソッドからオブジェクトを受け取ると、そのメソッドが戻った後に参照する必要があれば、そのオブジェクトを保持します。オブジェクトを参照する必要がなくなったメソッドは、解放する必要があります。オブジェクトの保持カウントがゼロの場合、メモリが解放され、オブジェクトは無効になります。オブジェクトが解放されると、Dealloc() メソッドが呼び出され、インスタンス変数の所有権を解放します。

この手法の問題の 1 つとして、オブジェクトのオーナー権限を譲渡する方法があります。たとえば、ファクトリ メソッドを呼び出してオブジェクトを作成できます。ファクトリ メソッドがオブジェクトを返す前にオブジェクトを解放すると(オブジェクトを所有しなくなるため)、呼び出し元メソッドがオブジェクトを保持する前にそのオブジェクトが解放されます。

オブジェクトのオーナー権限を譲渡するには、メソッドによりリリース メッセージではなく自動リリース メッセージを送信し、リリース メッセージを延期します。これにより、ファクトリ メソッドは、返されるオブジェクトを作成し、オブジェクトを無効にすることなくそのオーナー権限を放棄できます。一定の間隔(iOS アプリでの各イベントループの反復処理の後など)で、自動リリース プールは「ドレイン」されます。つまり、そのプール内のすべてのオブジェクトに遅延リリース メッセージが送信されます。保持数がゼロになったオブジェクトは、通常どおり解放されます。

メモリ管理の負担はデベロッパーが負うため、参照カウント方式ではメモリがリークしやすくなります。ただし、Apple はこの問題を最小限に抑えるためのベスト プラクティスを推奨しており、J2ObjC が実装しています。

また、メモリリークを検出するためのランタイムとツールのサポートもあります。Objective-C ランタイムは、アプリの終了時に検出されたリークを報告します。これが、J2ObjC が JUnit テストを実行可能なバイナリに変換する理由の一つです。Xcode は Clang を使用しており、コンパイラはメモリの問題に関する優れた静的解析を行います。Xcode はそれを Analyze コマンドで利用できるようにします。

自動参照カウント(ARC)

ARC は Apple が推奨するメモリ管理方法です。参照カウントの役割をコンパイラが担うことで、コンパイル時に適切な remember、release、autorelease の各メソッドが追加されます。ARC は、iOS 5 以降を搭載したデバイス向けの弱参照をサポートしています。

プロジェクトでは、翻訳したコードには ARC を使用することをおすすめします。トランスパイルされた Objective-C コードは、手書きの Objective-C コードのようなものです。ARC を使用すると、デベロッパーは過剰な解放や参照不足などの一般的なメモリ関連のエラーを回避できるため、メモリ管理がシンプルになり、エラーが発生しにくくなります。

デフォルトでは、ARC は例外セーフではありません。具体的には、例外がスローされたときにメモリリークが発生します。例外は Java ではより一般的であり、通常は復元可能であるため、問題になる可能性があります。円弧でコンパイルするときに -fobjc-arc-exceptions を使用すると、許容可能なパフォーマンス コストでリークが修正されます。

また、新しいプロジェクトでは、手書きの Objective-C コードには ARC を使用し、プロファイリング データに重大なパフォーマンスの問題を示す場合にのみ手動の参照カウントにフォールバックすることをおすすめします。ARC コードと非 ARC コードのどちらでも、問題なくコンパイルして同じアプリにリンクできます。

弱い参照

フィールドには、com.google.devtools.j2objc.Weak のアノテーションを付けることができます。Transpiler は、これを使用して Objective-C の弱参照セマンティクスに従うフィールドを生成します。参照カウントを使用する場合、フィールドは初期化時に保持されず、含まれるインスタンスが解放されると自動解放されます。ARC では、弱いフィールドは __unsafe_unretained アノテーションでマークされ、関連するプロパティは弱いと宣言されます。

場合によっては、内部クラスのインスタンスは外部インスタンスとともに参照サイクルに入ることがあります。ここでは、com.google.devtools.j2objc.WeakOuter アノテーションを使用して内部クラスをマークしているため、外部クラスへの参照は上記のとおり扱われます。内部クラスの他のフィールドは、このアノテーションの影響を受けません。

J2ObjC は WeakReference クラスもサポートしているため、J2ObjC を使用する Java コードも変換時に同じように動作します。WeakReference は本質的に JVM 上で非決定的であるので注意してください。予測可能性を維持しながらメモリリークを回避したいアプリでは、@Weak@WeakOuter を使用することをおすすめします。

メモリ管理ツール

  • Cycle Finder ツール - Java ソースファイルを分析して、強力なオブジェクト参照サイクルを特定します。
  • Xcode Instruments - Xcode のプロファイリング ツールスイート。
  • Xcode Memory Diagnostics - メモリ診断とロギングを使用して実行するためのビルド オプション。