ビルドプロセス中、Unity はマネージコードストリッピングと呼ばれるプロセスを通じて、使用されていないコードや到達できないコードを削除し、アプリケーションの最終サイズを大幅に縮小します。マネージコードストリッピングは、プロジェクト内の C# スクリプトからビルドされたアセンブリ、パッケージやプラグインの一部であるアセンブリ、.NET Framework のアセンブリなどのマネージアセンブリからコードを削除します。
Unity は、Unity リンカーと呼ばれるツールを使用して、プロジェクトのアセンブリ内のコードの静的分析を実行します。静的分析では、実行中にアクセスできないクラス、クラスの一部、関数、関数の一部が特定されます。この分析はビルド時に存在するコードのみが対象です。静的分析の実行時には、ランタイムに生成されるコードは存在しないためです。
Managed Stripping Level 設定を使用して、Unity がプロジェクトに対して実行するコードのストリッピングレベルを設定できます。コードの特定の部分を削除しないようにするには、アノテーションを使用して、保持するべきコードベースの部分を Unity リンカーに指示します。詳細については、Unity リンカー を参照してください。
Managed Stripping Level プロパティでは、Unity リンカーがアプリケーションのコードを解析して削除するときに従うルールセットを定義します。設定を Minimal から High に上げると、ルールにより、リンカーは到達不可能なコードを探すために、より多くのアセンブリを検索するようになります。Unity リンカーは、レベルを高くするとより多くのコードを削除し、ビルドの最終サイズを縮小しますが、検索範囲が広がるため、各ビルドの生成に必要な時間が増えます。
Managed Stripping Level プロパティを変更するには、以下を行います。
| プロパティ | 機能 |
|---|---|
| Disabled | Unity はコードを削除しません。 この設定は、Mono スクリプティングバックエンドを使用する場合にのみ表示されるデフォルトの設定です。 |
| 最低限 | UnityEngine と .NET クラスライブラリのみを検索して、未使用のコードを探します。ユーザーが作成したコードは削除しません。この設定は、ランタイムの予期しない動作を引き起こす可能性が最も低くなります。 この設定は、ビルドサイズよりも使いやすさを優先するプロジェクトに役立ちます。IL2CPP スクリプティングバックエンドを使用する場合のデフォルト設定です。 |
| 低 | ユーザーが作成したアセンブリの一部とすべての UnityEngine および .NET クラスライブラリを検索して、未使用のコードを探します。この設定は、未使用のコードの一部を削除するルールセットを適用しますが、意図しない結果 (リフレクションを使用するランタイムコードの動作の変更など) を招く可能性を最小限に抑えます。 |
| 中 | すべてのアセンブリを部分的に検索して、到達不可能なコードを見つけます。この設定は、ビルドサイズを減らすために、より多くの種類のコードパターンを削除するルールセットを適用します。到達不可能なコードをすべて見つけて削除するわけではありませんが、この設定により、望ましくないまたは予期しない動作変更のリスクが高まります。 |
| 高 | すべてのアセンブリに広範な検索を実行して、到達不可能なコードを見つけます。この設定では、コードの安定性よりもサイズ削減を優先し、可能な限り多くのコードを削除します。 この検索は、低いストリッピングレベルの場合よりも大幅に時間がかかる場合があります。この設定は、ビルドサイズを小さくすることが極めて重要なプロジェクトにのみ使用します。アプリケーションを十分にテストし、 [Preserve] 属性と link.xml ファイルを慎重に使用して、Unity リンカーによって重要なコードが削除されないようにしてください。 |
アノテーションを使用して、Unity リンカーがコードの特定のセクションを削除しないようにすることができます。これは、(例えばリフレクションなどによる) 静的分析の実行時に、存在しないランタイムコードが生成される場合に役立ちます。アノテーションは、Unity リンカーに、削除しないコードパターンに関する一般的なガイダンスを提供するか、特定の定義したコードセクションを削除しないよう指示します。
マネージコードのストリッピングからコードを保護するためのアノテーションには、大きく分けて 2 つの方法があります。
これらの技法は、Unity リンカーが高いストリッピングレベルで削除するコード量をより細かく制御し、重要なコードが削除されてしまう可能性を減らします。アノテーションは、コードがリフレクションを通じて他のコードを参照する場合に特に便利です。なぜなら、Unity リンカーは必ずしもリフレクションの使用を検出できるとは限らないからです。
リフレクションを使用するコードやランタイムに他のコードを生成するコードを保持することで、アプリケーションの実行時に予期しない動作が発生する可能性を大幅に減らすことができます。Unity リンカーが認識できるリフレクションパターンの例については、Unity Intermediate Language Linker リフレクションテストスイート を参照してください。
ルートアノテーションは、Unity リンカーがコードの要素をルートとして扱うようにすることで、コードストリッピングプロセスで削除されないようにします。使用できるルートアノテーションには 2 つの種類があり、個々の型をコンストラクターやアセンブリとともに保持する必要があるかどうかによって異なります。
Preserve 属性を使用すると、コードの特定のセクションを Unity リンカーの静的分析から個別に除外することができます。コードの一部にこの属性をアノテーションとして加えるには、保持したいコードの最初の部分の直前に [Preserve] を追加します。以下のリストは、[Preserve] 属性を使用してさまざまなコード要素に注釈を付けるときに、Unity リンカーが保持するエンティティを示しています。
[Preserve] 属性をアセンブリに割り当てるには、アセンブリに含まれる任意の C# ファイルの名前空間宣言の前に属性宣言を配置します。[add] アクセサー、[remove] アクセサーを保持します。
[Preserve] 属性は、ある型とそのデフォルトコンストラクターの両方を保持したい場合に使用します。両方ではなくどちらか一方を残したい場合は、link.xml ファイルを使用します。
[Preserve] 属性は、すべてのアセンブリと名前空間で定義できます。UnityEngine.Scripting.PreserveAttribute クラスを使用したり、UnityEngine.Scripting.PreserveAttribute のサブクラスを作成したり、独自の PreserveAttribute クラスを作成したりすることができます。
例:
class Foo
{
[Preserve]
public void PreservedMethod(){}
}
link.xml という名前の .xml ファイルをプロジェクトに加えると、特定のアセンブリまたはアセンブリの一部のリストを保持できます。link.xml ファイルは、プロジェクトの Assets フォルダーまたは Assets フォルダーのサブディレクトリに存在し、ファイルに <linker> タグが含まれている必要があります。Unity リンカーは、 link.xml ファイルに保存されているアセンブリ、型、メンバーをルート型として扱います。
プロジェクト内の link.xml ファイル数に制限はありません。そのため、各プラグインに対して個別の保持宣言を提供できます。link.xml ファイルをパッケージに加えることはできませんが、パッケージ外の link.xml ファイルからパッケージアセンブリを参照することはできます。
以下の例は、link.xml ファイルを使用して、プロジェクトのアセンブリのルートタイプを宣言できるさまざまな方法を示しています。
<linker>
<!--Preserve types and members in an assembly-->
<assembly fullname="AssemblyName">
<!--Preserve an entire type-->
<type fullname="AssemblyName.MethodName" preserve="all"/>
<!--No "preserve" attribute and no members specified means preserve all members-->
<type fullname="AssemblyName.MethodName"/>
<!--Preserve all fields on a type-->
<type fullname="AssemblyName.MethodName" preserve="fields"/>
<!--Preserve all fields on a type-->
<type fullname="AssemblyName.MethodName" preserve="methods"/>
<!--Preserve the type only-->
<type fullname="AssemblyName.MethodName" preserve="nothing"/>
<!--Preserving only specific members of a type-->
<type fullname="AssemblyName.MethodName">
<!--Fields-->
<field signature="System.Int32 FieldName" />
<!--Preserve a field by name rather than signature-->
<field name="FieldName" />
<!--Methods-->
<method signature="System.Void MethodName()" />
<!--Preserve a method with parameters-->
<method signature="System.Void MethodName(System.Int32,System.String)" />
<!--Preserve a method by name rather than signature-->
<method name="MethodName" />
<!--Properties-->
<!--Preserve a property, its backing field (if present),
getter, and setter methods-->
<property signature="System.Int32 PropertyName" />
<property signature="System.Int32 PropertyName" accessors="all" />
<!--Preserve a property, its backing field (if present), and getter method-->
<property signature="System.Int32 PropertyName" accessors="get" />
<!--Preserve a property, its backing field (if present), and setter method-->
<property signature="System.Int32 PropertyName" accessors="set" />
<!--Preserve a property by name rather than signature-->
<property name="PropertyName" />
<!--Events-->
<!--Preserve an event, its backing field (if present), add, and remove methods-->
<event signature="System.EventHandler EventName" />
<!--Preserve an event by name rather than signature-->
<event name="EventName" />
</type>
</assembly>
</linker>
次の例は、アセンブリ全体を宣言する方法を示しています。
<!--Preserve an entire assembly-->
<assembly fullname="AssemblyName" preserve="all"/>
<!--No "preserve" attribute and no types specified means preserve all-->
<assembly fullname="AssemblyName"/>
<!--Fully qualified assembly name-->
<assembly fullname="AssemblyName, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="AssemblyName.Foo" preserve="all"/>
</assembly>
<!--Force an assembly to be processed for roots but don’t explicitly preserve anything in particular. Useful when the assembly isn't referenced.-->
<assembly fullname="AssemblyName" preserve="nothing"/>
この例では、ネスト型または汎用型のいずれかを保持する方法を示しています。
<!--Examples with generics-->
<type fullname="AssemblyName.G`1">
<!--Preserve a field with generics in the signature-->
<field signature="System.Collections.Generic.List`1<System.Int32> FieldName" />
<field signature="System.Collections.Generic.List`1<T> FieldName" />
<!--Preserve a method with generics in the signature-->
<method signature="System.Void MethodName(System.Collections.Generic.List`1<System.Int32>)" />
<!--Preserve an event with generics in the signature-->
<event signature="System.EventHandler`1<System.EventArgs> EventName" />
</type>
<!--Preserve a nested type-->
<type fullname="AssemblyName.H/Nested" preserve="all"/>
<!--Preserve all fields of a type if the type is used. If the type isn't used, it will be removed-->
<type fullname="AssemblyName.I" preserve="fields" required="0"/>
<!--Preserve all methods of a type if the type is used. If the type isn't used, it will be removed-->
<type fullname="AssemblyName.J" preserve="methods" required="0"/>
<!--Preserve all types in a namespace-->
<type fullname="AssemblyName.SomeNamespace*" />
<!--Preserve all types with a common prefix in their name-->
<type fullname="Prefix*" />
link.xml ファイルの <assembly> 要素には、3 つの特別な用途の属性があり、アノテーションをより詳細に制御できます。
ignoreIfMissing: すべてのプレイヤービルド中に存在しないアセンブリの保持を宣言する必要がある場合に、この属性を使用します。<linker>
<assembly fullname="Foo" ignoreIfMissing="1">
<type name="TypeName"/>
</assembly>
</linker>
ignoreIfUnreferenced: そのアセンブリが別のアセンブリによって参照されている場合にのみ、アセンブリのエンティティを保持したい場合があります。この属性を使用すると、アセンブリ内で少なくとも 1 つの型が参照されている場合のみ、アセンブリ内のエンティティを保持します。<linker>
<assembly fullname="Bar" ignoreIfUnreferenced="1">
<type name="TypeName"/>
</assembly>
</linker>
windowsruntime: Windows Runtime Metadata (.winmd) アセンブリの保持を定義する場合は、link.xml ファイルの <assembly> 要素に windowsruntime 属性を加える必要があります。<linker>
<assembly fullname="Windows" windowsruntime="true">
<type name="TypeName"/>
</assembly>
</linker>
依存関係アノテーションは、さまざまなコード要素間の依存関係を定義します。リフレクションのように Unity リンカーが静的に分析できないコードパターンを保持するのに便利です。また、依存関係アノテーションは、コード要素がどのルート要素にも使用されない場合に、誤ってコード要素を保持しないようにします。Unity リンカーがコード要素を処理する方法を変更するには、以下の 2 つの方法を使用します。
[Preserve] 属性は、API が常に必要な場合に便利です。他の属性は、より一般的な保持に役立ちます。例えば、RequireImplementorsAttribute を使ってインターフェースに注釈を付けると、特定のインターフェースを実装するすべての型を保持することができます。
特定のコーディングパターンを注釈するには、以下の属性を 1 つ以上使用します。
これらの属性をさまざまな方法で組み合わせることで、Unity リンカーがコードを保持する方法をより正確に制御することができます。
[assembly: UnityEngine.Scripting.AlwaysLinkAssembly] 属性は、ビルドに含まれる別のアセンブリによってそのアセンブリが参照されているかどうかにかかわらず、Unity リンカーに強制的にアセンブリを検索させます。AlwaysLinkAssembly 属性はアセンブリにのみ適用できます。
この属性は、直接アセンブリ内のコードを保持するわけではありません。代わりに、ルートをマークするルールをアセンブリに適用するよう Unity リンカーに指示します。アセンブリのルートマーキングルールに一致するコード要素がない場合は、Unity リンカーはビルドからアセンブリを削除します。
この属性は、[RuntimeInitializeOnLoadMethod] 属性を持つ 1 つ以上のメソッドを含むプリコンパイルされたアセンブリまたはパッケージアセンブリで使用します。ただし、プロジェクト内の任意のシーンで直接または間接的に使用される型が含まれない場合があります。
アセンブリが [assembly: AlwaysLinkAssembly] を定義し、かつ、ビルドに含まれる別のアセンブリによって参照されている場合、属性は結果に影響しません。