Unity 5.3.x build size increase FAQ

Recently we are seeing number of questions scattered around forums regarding build size increase when building iOS applications with Unity 5.3.x. This post aims to clarify some aspects of it in single place.

1. My application build size increased by 130-150 MB after switching to Unity 5.3.x. What’s happening?
We enabled Bitcode support as default for iOS projects. It’s new Apple’s technology, which allows them to re-optimize your application after submission to the App Store. Though it requires to send way more information about your application as part of submission. When your application gets processed by App Store this additional information will be stripped away from your package and won’t increase final download size for your users. This technology is already mandatory for tvOS and watchOS applications, so we are enabling it for iOS to help you prepare ahead of time to make sure that your applications are fully compatible when/if it becomes mandatory for iOS. Description of Bitcode technology is available at: https://developer.apple.com/library/tvos/documentation/IDEs/Conceptual/AppDistributionGuide/AppThinning/AppThinning.html.

2. How can I measure Bitcode size added to my application?
You should make a build of your application, locate where Xcode puts built files and navigate Terminal.app to that folder. Then just run:

otool -l <your_app_name>.app/<your_app_name>[/code]

Inspect its output and look for something like “segname __LLVM”:

cmd LC_SEGMENT
cmdsize 124
segname __LLVM
vmaddr 0x00fac000
vmsize 0x07ce0000
fileoff 15564800
filesize 130940928

The “filesize” attribute is your Bitcode size. Note, if you have build for more than one architecture there could couple of such segments in your app.

3. How can I disable Bitcode for my test builds made by automated build system?
To automate this task we recommend adding Editor build post-processing script:

[PostProcessBuild]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget == BuildTarget.iOS)
        {
            string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";

            PBXProject proj = new PBXProject();
            string nativeTarget = proj.TargetGuidByName(Xcode.PBXProject.GetUnityTargetName());
            string testTarget = proj.TargetGuidByName(Xcode.PBXProject.GetUnityTestTargetName());
            string[] buildTargets = new string[]{nativeTarget, testTarget};

            proj.ReadFromString(File.ReadAllText(projPath));
            proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");
            File.WriteAllText(projPath, proj.WriteToString());
        }
    }

More example code is available here: https://bitbucket.org/Unity-Technologies/iosnativecodesamples/src/28ca3fca5740f23fb6116855c13c5fdd415ac5d5/NativeIntegration/Misc/UpdateXcodeProject/?at=5.0-dev

4. Testflight download size looks quite bigger that I expected. Any advise on it?
Testflight test build download size does not really represent how much your app will take on actual App Store, because they are going through very different preparation pipelines. It’s not unusual for testflight downloads to be bigger than App Store submission size, because they might include debug symbols and other additional information.

5. Where can I read more on iOS application size analysis?
Please check out this forum post: IL2CPP build size improvements - Unity Engine - Unity Discussions

Edit: added sample code for disabling Bitcode in an automatic way

8 Likes

Thanks for the post, It was informative but did not solve my problems with cloud build.

As expected, upgrading from 5.2 - 5.3 pushed my development build from 31MB - > 219MB’s. Each launch of the app gets stuck at the splash screen.

I tried implementing your solution to disable bit code. There were problmens:

This line of code calls a method that doesn’t seem to exist:

string projectTarget = proj.ProjectGuid();

but since the variable assigned is never even used I just got rid of it.

Pushing this up to my cloud build repository had no effect.

Relying on the [PostProcessBuild] attribute instead setting up cloud build to call a specified postprocess seems to not work. This method exists in my editor folder, in a static build settings class.

The signature of OnPostprocessBuild is different from what cloud build expects.

No trailing parenthesis, and it can’t have the same name as your Pre-Export method! This method must accept a string parameter, which will receive the path to the exported Unity player (or Xcode project in the case of iOS).

Any idea’s how I can get this to work? My app works perfect when I manually build it locally, but spikes in size when build with cloud build and crashes at the splash screen.

@CostelloNicho

The UCB signature is different if you use a custom post process attribute. You either can use [PostProcessBuild] attribute directly or forward it from the custom post process:

    #if UNITY_CLOUD_BUILD
    /*
    This methods are per platform post export methods. They can be added additionally to the post process attributes in the Advanced Features Settings on UCB using
    - PostBuildProcessor.OnPostprocessBuildiOS
    - PostBuildProcessor.OnPostprocessBuildAndroid
    depending on the platform they should be executed.

    Here is the basic order of operations (e.g. with iOS operation)
    - Unity Cloud Build Pre-export methods run
    - Export process happens
    - Methods marked the built-in PostProcessBuildAttribute are called
    - Unity Cloud Build Post-export methods run
    - [unity ends]
    - (iOS) Xcode processes project
    - Done!
    More information can be found on http://forum.unity3d.com/threads/solved-ios-build-failed-pushwoosh-dependency.293192/
    */
    public static void OnPostprocessBuildiOS (string exportPath)
    {
        Debug.Log("[UCB] OnPostprocessBuildiOS");
        ProcessPostBuild(BuildTarget.iOS,exportPath);
    }
    public static void OnPostprocessBuildAndroid (string exportPath)
    {
        Debug.Log("[UCB] OnPostprocessBuildAndroid");
        ProcessPostBuild(BuildTarget.Android,exportPath);
    }
    #endif

    // a normal post process method which is executed by Unity
    [PostProcessBuild]
    public static void OnPostprocessBuild (BuildTarget buildTarget, string path)
    {
        #if !UNITY_CLOUD_BUILD
        Debug.Log ("[UCB] OnPostprocessBuild");
        ProcessPostBuild (buildTarget, path);
        #endif
    }

    private static void ProcessPostBuild (BuildTarget buildTarget, string path)
    {
         if (buildTarget == BuildTarget.iOS)
         {
           string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
           PBXProject proj = new PBXProject();
           string nativeTarget = proj.TargetGuidByName(Xcode.PBXProject.GetUnityTargetName());
           string testTarget = proj.TargetGuidByName(Xcode.PBXProject.GetUnityTestTargetName());
           string[] buildTargets = new string[]{nativeTarget, testTarget};
           proj.ReadFromString(File.ReadAllText(projPath));
           proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");        
           File.WriteAllText(projPath, proj.WriteToString());
       }
    }

Please let us know if this would not work.

1 Like

Thanks David. Had to change one thing:

BuildTarget.iPhone
to
BuildTarget.iOS

Even after that still got the signature error:

Here was a previous error:

@David-Berger

I see two issues with your example code.

BuildTarget.iPhone != BuildTarget.iOS
Meaning the statement on row 42 will allways be false when cloud build.
I event get a compile error if I have BuildTarget.iPhone in my code.
2.
Is PBXProject different from Xcode.PBXProject ? I get error if I use Xcode.PBXProject.
Can we see the entire class? Including the usings in the beginning.

Edit: Ops, Costello beat me to it.

1 Like

@zworp : In regards to #2, PBXProject is under the following namespace that can be surrounded with a directive to only include for iOS builds:

#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif

For #1, I’m pretty sure that the build target enum changed and iOS is now the proper build target to use.

After trying the various approaches I read in several posts here’s what I believe is the correct solution:

[PostProcessBuild]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget == BuildTarget.iOS)
        {
            string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
            PBXProject proj = new PBXProject();
            proj.ReadFromString(File.ReadAllText(projPath));

            string nativeTarget = proj.TargetGuidByName(PBXProject.GetUnityTargetName());
            string testTarget = proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
            string[] buildTargets = new string[]{nativeTarget, testTarget};

            proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");
            File.WriteAllText(projPath, proj.WriteToString());
        }
    }

The main problem I had was the proj.ReadFromString(File.ReadAllText(projPath)); being placed at the wrong spot. It makes sense to call this method right after the PBXProject object creation so it’s populated with the correct project’s datas.

I went from 145mb to 14.2mb on a near-empty project.

Of course, as stated by Mantas Puida, it will have no impact over the final download size but it can help you approximating the final build size of your game.

Hope that helped.

2 Likes

Thank you!
This version worked for me.

Hope Unity will update the blog post with a working example.

This worked for me but only when I build locally. When built through cloud build the app freezes at the splash screen. Both the cloud build and the manual build are on the same commit. I’m getting no error / warning in the cloud build logs.

I doubt it has anything to do with cloud build. The postProcess just disable bitcode so I don’t see any reason for your project to freeze at the splash screen.

Your best shot is to get some kind of reporter within your game to check for any error at runtime. If you are stuck on Unity’s splashscreen it might not work as it’s too early in the process but it’s worth a shot.

Maybe we should continue this discussion on another thread?

1 Like

Hi,

So i have been making iOS builds of my game using latest unity (5.3.2) and Xcode 7.2 with bit code enabled to true. The app size was 69.8MB. Now with CloudBuild the IPA became 283.9MB. I do agree that bitcode increases app size but same setting on my machine gives 69.8MB and cloud build is 283.

In cloud build log i did notice one thing
[Unity] WARNING: PVRTC texture format is not supported, decompressing texture

We use PVRTC4 bits for almost all texture since its the best format for IOS. Is there an issue with Unity Could to understand this format?

I guess cloud build is un-compressing all textures which results in huge app size increase

Edit:

1 Like

@Inverse101 that’s Unity Cloud Build specific, it would be great if you could create a post about it there. But you are right and we already work on a solution to the problem (773054).

@Inverse101 just to make sure you know that real bitcode is being added only when executing a archive action in XCode, that could maybe explain the difference between your local and cloud build.

Exception: ExtractAssemblyTypeInfo: Failed to process Library/ScriptAssemblies/Assembly-CSharp.dll, System.ArgumentException: An element with the same key already exists in the dictionary.
at System.Collections.Generic.Dictionary2[System.UInt32,System.UInt32].Add (UInt32 key, UInt32 value) [0x0007e] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:404 at Mono.Cecil.MetadataSystem.SetReverseNestedTypeMapping (UInt32 nested, UInt32 declaring) [0x00000] in <filename unknown>:0 at Mono.Cecil.MetadataReader.AddNestedMapping (UInt32 declaring, UInt32 nested) [0x00000] in <filename unknown>:0 at Mono.Cecil.MetadataReader.InitializeNestedTypes () [0x00000] in <filename unknown>:0 at Mono.Cecil.MetadataReader.InitializeTypeDefinitions () [0x00000] in <filename unknown>:0 at Mono.Cecil.MetadataReader.ReadTypes () [0x00000] in <filename unknown>:0 at Mono.Cecil.ModuleDefinition.<get_Types>b__8 (Mono.Cecil.ModuleDefinition _, Mono.Cecil.MetadataReader reader) [0x00000] in <filename unknown>:0 at Mono.Cecil.ModuleDefinition.Read[ModuleDefinition,TypeDefinitionCollection] (Mono.Cecil.TypeDefinitionCollection& variable, Mono.Cecil.ModuleDefinition item, System.Func3 read) [0x00000] in :0
at Mono.Cecil.ModuleDefinition.get_Types () [0x00000] in :0
at UnityEditor.AssemblyTypeInfoGenerator.GatherClassInfo () [0x0001e] in C:\buildslave\unity\build\Editor\Mono\BuildPipeline\AssemblyTypeInfoGenerator.cs:392
at UnityEditor.AssemblyHelper.ExtractAssemblyTypeInfo (BuildTarget targetPlatform, Boolean isEditor, System.String assemblyPathName, System.String[ ] searchDirs) [0x00066] in C:\buildslave\unity\build\Editor\Mono\AssemblyHelper.cs:319
UnityEditor.AssemblyHelper.ExtractAssemblyTypeInfo (BuildTarget targetPlatform, Boolean isEditor, System.String assemblyPathName, System.String[ ] searchDirs) (at C:/buildslave/unity/build/Editor/Mono/AssemblyHelper.cs:323)
UnityEditor.HostView:OnGUI()

@Mantas-Puida
I create a empty project and build to xcode, there is a file named libiPhone-lib.a which size is 1.1G! And I archive this xcode project,I get a ipa with 560M. I uploaded a ipa with 650M early, the down load file size is 320M(on iphone)! So that means even I create a empty project, the down load size would be over 200M! What can I do to make my pack smaller? I aways make sure the Strip Engine Code is checked.
My build have to include ARMv7&ARM64.If I choose ARM64 only(or Mono2),I can’t run app on device from xocde. And if I choose ARMv7 only, I can’t upload at all because apple require ARM64 be included. LibiPhone-lib.a’s size always be 1.1G with IL2CPP(no matter ARMv7 only or ARM64 only),but Mono2 will change the size to 500M(mono2 not include ARM64).

1 Like

I created empty project with Unity 5.3.4p1. Archived it in Xcode 7.3 and exported with distribution profile for App Store deployment and did following analysis…

  • IPA size was: 159MB (Unity-iPhone.ipa)

Then I extracted it and got following folders:

  • 18MB BCSymbolMaps (these are symbol files supporting Bitcode, won’t be delivered to end user)

  • 243MB Payload (includes game assets, extra files and executable, which was 238M)

  • 58MB Symbols (these are symbol files, won’t be delivered to end user)

Now I had to analyze Payload, which was mostly executable + standard engine assets. Since assets were only ~5 MB, I won’t dig into that, but rather focus on executable. I analyzed it with otool, by running it twice: 1) otool -arch armv7 -l exec_name, 2) otool -arch arm64 -l exec_name. Then I checked included segment sizes:

  • armv7:

  • TEXT segment (code): 7.5 MB

  • DATA segment (various static fields for code and il2cpp metadata): 0.3 MB

  • LLVM (Bitcode) segment: 111.1 MB (will be stripped by Apple servers and won’t be delivered to end users).

  • LINKEDIT (linking with dynamic libraries) segment: 0.9 MB

  • arm64:

  • TEXT segment (code): 8.4 MB

  • DATA segment (various static fields for code and il2cpp metadata): 0.5 MB

  • LLVM (Bitcode) segment: 110.2 MB (will be stripped by Apple servers and won’t be delivered to end users).

  • LINKEDIT (linking with dynamic libraries) segment: 0.8 MB

Exec size without Bitcode for armv7 is 8.7 MB and for arm64 it is 9.7 MB.
Total exec size without Bitcode: 18.4 MB
Estimated installation size (exec + assets): 23.4 MB
Expected download size without app thinning : 1.25 MB (compressed assets) + 15.9 MB (TEXT segment, which doesn’t compress well because of encryption) + 0.5 MB (DATA+LINKEDIT, they compress pretty well) = 17.65 MB

5 Likes

hi @Mantas-Puida , thanks a lot for that,

are you basically saying that the reports of large file sizes, are essentially wrong? ie, once the apps are ACTUALLY delivered to iTunes users, Apple does NOT deliver the ridiculous bitcode nonsense and the size returns to conventional values?

Please explain, and thanks.

Another straightforward question, @Mantas-Puida … when you upload finally to iTunes (either for TFA use or for actual submission of a new version),

quite simply you can check “off” uploading the bitcode crap.

I’m surprised this hasn’t been mentioned (often) in this whole discussion.

What’s your take on that? Thanks.

Even if bitcode adds a lot to your executable file, it does not represent your final ipa size (the one that your users will download). The best and reliable way to check your final ipa size is to upload it on Testflight.

"Bitcode is an intermediate representation of a compiled program. Apps you upload to iTunes Connect that contain bitcode will be compiled and linked on the store. Including bitcode will allow Apple to re-optimize your app binary in the future without the need to submit a new version of your app to the store."

You can check off bitcode or simply disabling it for now and that will work…for now. In the future Apple will probably make it mandatory so sooner or later you will be forced to let it on.

Yes, that’s correct, the whole point of this discussion is that Bitcode does not increase final (user facing) download size of your app. Submitting Bitcode with your builds early will ensure that you won’t get surprises when this becomes mandatory (as it is already for tvOS).