Version: 2021.3
언어: 한국어
스크립팅 제약
Unity linker

관리되는 코드 스트리핑

빌드 프로세스 중에 Unity는 관리되는 코드 스트리핑이라는 프로세스를 통해 사용하지 않거나 도달할 수 없는 코드를 제거하므로 애플리케이션의 최종 빌드 크기를 대폭 줄일 수 있습니다. 관리되는 코드 스트리핑은 프로젝트의 C# 스크립트에서 빌드된 어셈블리, 패키지와 플러그인의 일부인 어셈블리, .NET 프레임워크의 어셈블리를 포함한 관리되는 어셈블리에서 코드를 제거할 수 있습니다.

Unity는 Unity linker라는 툴을 사용하여 프로젝트 어셈블리의 코드에 대한 정적 분석을 수행합니다. 정적 분석은 실행 중에 도달할 수 없는 모든 클래스, 클래스의 일부, 함수, 함수의 일부를 식별합니다. Unity가 정적 분석을 수행할 때 런타임 생성 코드가 존재하지 않기 때문에 이 분석에는 빌드 시 존재하는 코드만 포함됩니다.

Managed Stripping Level 설정을 사용하여 Unity가 프로젝트에 대해 수행하는 코드 스트리핑 레벨을 구성할 수 있습니다. Unity가 코드의 특정 부분을 제거하지 못하도록 하려면 주석을 사용하여 Unity linker가 보존해야 하는 코드 베이스 부분을 표시합니다. 자세한 내용은 Unity linker를 참조하십시오.

관리되는 코드 스트리핑 설정

Managed Stripping Level 프로퍼티
Managed Stripping Level 프로퍼티

Managed Stripping Level 프로퍼티는 Unity linker가 애플리케이션 코드를 분석하고 스트리핑할 때 따르는 규칙 집합을 결정합니다. 설정을 Minimal에서 High로 높이면 linker가 도달할 수 없는 코드에 대해 더 많은 어셈블리를 검색할 수 있습니다. Unity linker는 더 높은 설정에서 더 많은 코드를 제거하여 빌드의 최종 크기를 줄입니다. 하지만 확장된 검색은 각 빌드를 생성하는데 시간이 더 오래 걸린다는 것을 의미합니다.

Managed Stripping Level 프로퍼티를 변경하려면 다음 단계를 따르십시오.

  1. Edit > Project Settings > Player로 이동합니다.
  2. Other Settings에서 Optimization 제목으로 이동합니다.
  3. Managed Stripping Level 프로퍼티를 원하는 값으로 설정합니다.
프로퍼티: 기능:
Disabled Unity는 코드를 제거하지 않습니다.

이 설정은 Mono 스크립팅 백엔드를 사용하는 경우에만 표시되며 기본 설정입니다.
Minimal Unity는 UnityEngine과 .NET 클래스 라이브러리에서 사용하지 않는 코드만 검색합니다. 사용자가 작성한 코드는 제거하지 않습니다. 이 설정은 예기치 않은 런타임 동작을 일으킬 가능성이 가장 낮습니다.

이 설정은 가용성이 빌드 크기보다 우선시되는 프로젝트에 유용합니다. IL2CPP 스크립팅 백엔드를 사용하는 경우 기본 설정입니다.
낮음 Unity는 사용자가 작성한 일부 어셈블리와 모든 UnityEngine과 .NET 클래스 라이브러리에서 사용하지 않는 코드를 검색합니다. 이 설정은 일부 사용하지 않는 코드를 제거하지만 반사를 사용하는 런타임 코드의 동작 변경과 같은 의도하지 않은 결과가 발생할 가능성을 최소화하는 규칙 집합을 적용합니다.
중간 Unity는 도달할 수 없는 코드를 찾기 위해 모든 어셈블리를 부분적으로 검색합니다. 이 설정은 빌드 크기를 줄이기 위해 더 다양한 타입의 코드 패턴을 스트리핑하는 규칙 집합을 적용합니다. 도달할 수 없는 모든 코드를 Unity가 스트리핑하지는 않지만 이 설정은 바람직하지 않거나 예기치 않은 동작 변경의 위험을 높입니다.
높음 Unity는 도달할 수 없는 코드를 찾기 위해 모든 어셈블리를 광범위하게 검색합니다. 이 설정에서 Unity는 코드 안정성보다 크기 축소를 우선시하여 최대한 많은 코드를 제거합니다.

이 검색은 낮은 스트리핑 레벨보다 훨씬 더 오래 걸릴 수 있습니다. 이 설정은 작은 빌드 크기가 매우 중요한 프로젝트에만 사용합니다. 애플리케이션을 면밀히 테스트하고 [Preserve] 속성과 link.xml 파일을 주의해서 사용하여 Unity linker가 중요한 코드를 스트리핑하지 않도록 확인합니다.

주석을 사용한 코드 보존

주석을 사용하여 Unity linker가 코드의 특정 섹션을 스트리핑하지 못하도록 할 수 있습니다. 예를 들어 Unity가 반사를 통해 정적 분석을 수행할 때 존재하지 않는 런타임 코드를 애플리케이션이 생성하는 경우에 유용합니다. 주석은 Unity linker에 스트리핑해서는 안 되는 코드 패턴에 대한 일반적인 지침을 제공하거나 정의된 특정 코드 섹션을 스트리핑하지 말라는 지침을 제공합니다.

관리되는 코드 스트리핑 프로세스에서 코드를 보존하기 위해 코드에 주석을 추가할 수 있는 다음 두 가지 광범위한 접근 방식이 있습니다.

  • 루트 주석은 코드의 일부를 루트로 식별합니다. Unity linker는 루트로 표시된 코드를 스트리핑하지 않습니다. 루트 주석은 사용하기에 덜 복잡하지만 Unity linker가 스트리핑해야 하는 일부 코드를 보존할 수도 있습니다.
  • 종속성 주석은 코드 요소 간의 연결을 정의합니다. 종속성 주석은 루트 주석에 비해 코드의 과도한 보존을 줄일 수 있습니다.

이러한 각 기술은 더 높은 스트리핑 레벨에서 Unity linker가 스트리핑하는 코드의 양을 더 많이 제어하고 중요한 코드가 스트리핑될 가능성을 줄입니다. Unity linker가 항상 반사 사용을 감지할 수는 없기 때문에 주석은 코드에서 반사를 통해 다른 코드를 참조할 때 특히 유용합니다.

반사를 사용하는 코드를 보존하거나 런타임 시 다른 코드를 생성하여 애플리케이션이 실행될 때 예기치 않은 동작의 가능성을 대폭 줄입니다. Unity linker가 인식할 수 있는 반사 패턴의 예는 Unity Intermediate Language Linker reflection test suite를 참조하십시오.

루트 주석

루트 주석은 Unity linker가 코드 요소를 루트로 취급하도록 하여 코드 스트리핑 프로세스에서 스트리핑되지 않습니다. 생성자 또는 어셈블리와 함께 개별 타입을 보존해야 하는지에 따라 사용할 수 있는 두 가지 타입의 루트 주석이 있습니다.

  • Preserve 속성: 개별 타입을 보존하기 위해 루트로 주석을 답니다.
  • Link.xml: 어셈블리와 해당 어셈블리 내의 모든 타입 또는 기타 코드 엔티티에 루트로 주석을 달아 보존합니다.

Preserve 속성을 사용하여 루트에 주석 달기

Preserve 속성을 사용하여 Unity linker의 정적 분석에서 코드의 특정 섹션을 개별적으로 제외합니다. 이 속성으로 코드에 주석을 추가하려면 보존할 코드의 첫 번째 부분 바로 앞에 [Preserve]를 추가합니다. 다음 리스트는 다른 코드 요소에 [Preserve] 속성을 주석으로 달 때 Unity linker가 보존하는 엔티티를 설명합니다.

  • Assembly: 어셈블리에서 사용되고 정의된 모든 타입을 보존합니다. 어셈블리에 [Preserve] 속성을 할당하려면 어셈블리에 포함된 임의의 C# 파일의 네임스페이스 선언 전에 속성 선언을 추가합니다.
  • Type: 클래스 또는 타입과 해당 기본 생성자를 보존합니다.
  • Method: 메서드, 메서드를 선언하는 타입, 메서드가 반환하는 타입, 모든 인수의 타입을 보존합니다.
  • Property: 프로퍼티, 프로퍼티를 선언하는 타입, 프로퍼티의 값 타입, 프로퍼티 값을 가져오고 설정하는 메서드를 보존합니다.
  • Field: 필드, 필드 타입, 필드를 선언하는 타입을 보존합니다.
  • Event: 이벤트, 이벤트를 선언하는 타입, 이벤트가 반환하는 타입, [add] 접근자 및 [remove] 접근자를 보존합니다.
  • Delegate: 델리게이트 타입과 델리게이트가 호출하는 모든 메서드를 보존합니다.

타입과 기본 생성자를 모두 보존하려면 [Preserve] 속성을 사용합니다. 둘 중 하나만 유지하려면 link.xml 파일을 사용합니다.

모든 어셈블리와 네임스페이스에 [Preserve] 속성을 정의할 수 있습니다. UnityEngine.Scripting.PreserveAttribute 클래스를 사용하거나 UnityEngine.Scripting.PreserveAttribute 서브 클래스를 생성하거나 고유한 PreserveAttribute 클래스를 생성할 수 있습니다. 예를 들면 다음과 같습니다.

class Foo
{
    [Preserve]
    public void PreservedMethod(){}
}

Link XML 파일을 사용하여 루트에 주석달기

프로젝트에 link.xml이라는 .xml 파일을 포함하여 특정 어셈블리 또는 어셈블리의 일부 리스트를 보존할 수 있습니다. link.xml 파일은 프로젝트의 Assets 폴더 또는 Assets 폴더의 하위 디렉토리에 있어야 하며 파일에 <linker> 태그를 포함해야 합니다. Unity linker는 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, it's backing field (if present), 
          getter, and setter methods-->
      <property signature="System.Int32 PropertyName" />

      <property signature="System.Int32 PropertyName" accessors="all" />

      <!--Preserve a property, it's backing field (if present), and getter method-->
      <property signature="System.Int32 PropertyName" accessors="get" />

      <!--Preserve a property, it's 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, it's 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&lt;System.Int32&gt; FieldName" />

      <field signature="System.Collections.Generic.List`1&lt;T&gt; FieldName" />

      <!--Preserve a method with generics in the signature-->
      <method signature="System.Void MethodName(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />

      <!--Preserve an event with generics in the signature-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; 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*" />

추가 어셈블리 XML 속성

link.xml 파일의 <assembly> 요소에는 주석을 통한 더 많은 제어를 위해 활성화할 수 있는 특수한 목적을 지닌 3가지 속성이 있습니다.

  • ignoreIfMissing: 모든 플레이어 빌드 동안 존재하지 않는 어셈블리에 대해 보존을 선언해야 하는 경우 이 속성을 사용합니다.
<linker>
  <assembly fullname="Foo" ignoreIfMissing="1">
    <type name="TypeName"/>
  </assembly>
</linker>
  • ignoreIfUnreferenced: 어셈블리가 다른 어셈블리에 의해 참조되는 경우에만 해당 어셈블리의 엔티티를 보존할 수 있습니다. 어셈블리에서 하나 이상의 타입이 참조된 경우에만 어셈블리의 엔티티를 보존하려면 이 속성을 사용합니다.
<linker>
  <assembly fullname="Bar" ignoreIfUnreferenced="1">
    <type name="TypeName"/>
  </assembly>
</linker>
  • windowsruntime: Windows 런타임 메타데이터(.winmd) 어셈블리에 대한 보존을 정의하는 경우 link.xml 파일의 <assembly> 요소에 windowsruntime 속성을 추가해야 합니다.
<linker>
  <assembly fullname="Windows" windowsruntime="true">
    <type name="TypeName"/>
 </assembly>
</linker>

종속성 주석

종속성 주석은 다양한 코드 요소 간의 종속성을 정의합니다. 이러한 주석은 Unity linker가 반사와 같이 정적으로 분석할 수 없는 코드 패턴을 보존하는 데 유용합니다. 또한 이러한 코드 요소를 사용하는 루트 요소가 없을 때 해당 코드 요소가 잘못 보존되지 않도록 합니다. Unity linker가 코드 요소를 처리하는 방법을 변경하는 데 사용할 수 있는 두 가지 방법이 있습니다.

  • Annotation 속성: 이 속성은 Unity linker가 주석이 달린 타입에서 파생된 타입과 같은 특정 코드 패턴을 보존해야 함을 표시합니다.
  • AlwaysLinkAssemblyAttribute: 이 속성을 사용하여 Unity linker가 프로젝트의 다른 어셈블리에서 참조하지 않는 경우에도 어셈블리를 처리해야함을 표시합니다.

Annotation 속성

[Preserve] 속성은 API가 항상 필요한 상황에 유용합니다. 다른 속성은 더 일반적인 보존에 유용할 수 있습니다. 예를 들어 인터페이스에 RequireImplementorsAttribute 주석을 추가하여 특정 인터페이스를 구현하는 모든 타입을 보존할 수 있습니다.

특정 코딩 패턴을 주석에 추가하려면 다음 속성 중 하나 이상을 사용합니다.

Unity linker가 코드를 보존하는 방법을 더욱 정확하게 제어하기 위해 이러한 속성을 다양한 방식으로 결합할 수 있습니다.

AlwaysLinkAssembly 속성

[assembly: UnityEngine.Scripting.AlwaysLinkAssembly] 속성은 빌드에 포함된 다른 어셈블리가 해당 어셈블리를 참조하는지 여부와 관계없이 Unity linker가 어셈블리를 검색하도록 합니다. AlwaysLinkAssembly 속성은 어셈블리에만 적용할 수 있습니다.

해당 속성은 어셈블리 내에서 코드를 직접 보존하지 않습니다. 대신 이 속성은 Unity linker에 루트 마킹 규칙을 어셈블리에 적용하도록 명령합니다. 어셈블리에 대한 루트 마킹 규칙과 일치하는 코드 요소가 없으면 Unity linker는 계속해서 해당 어셈블리를 빌드에서 제거합니다.

[RuntimeInitializeOnLoadMethod] 속성이 정의된 메서드를 하나 이상 포함하되 프로젝트 씬에 직간접적으로 사용된 타입을 포함하지 않는 사전 컴파일된 어셈블리 또는 패키지 어셈블리에 이 속성을 사용하십시오.

어셈블리가 [assembly: AlwaysLinkAssembly]를 정의하며 빌드에 포함된 다른 어셈블리에 의해 참조되는 경우 이 속성은 결과에 영향을 미치지 않습니다.

스크립팅 제약
Unity linker