白黒羊

UnityのiOSビルドのフリーズを解消した話

何個か修正を追加してUnityEditor自体やいくつかのアセットをバージョンアップしてiOSビルドしたところ途中でフリーズして動かなくなる事態に見舞われました。

問題箇所

適当なintの値が与えられているFooEnumというEnumの3桁目までの値の大小を比較してくれるComparerOrderByで使おうとしたらダメでした。

public void main() {
    _map = new Dictionary<FooEnum, Bar>();
    
    // _mapに値を代入
    
    foreach(var tuple in 
        _map
            .OrderBy(pair => pair.Key,
                Comparer<FooEnum>.Create(
                    (left, right) =>
                    {            
                        var lv = (int) left;
                        var rv = (int) right;
                        return rv % 1000 - lv % 1000;
                    })) )
    // foreachで行う処理
}

修正版

別にIComparerを継承したクラスを作ってやると解決しました。

public void main() {
    _map = new Dictionary<FooEnum, Bar>();    
    // _mapに値を代入

    var comparer = new FooEnumComparer();
    var sorted = new SortedDictionary<FooEnum, Bar>(
         _map
         .ToDictionary(pair => pair.Key,
             pair => pair.Value), comparer);
    
    foreach(var tuple in sorted)  // foreachで行う処理
}

public class FooEnumComparer : IComparer<FooEnum>
{
    public int Compare(FooEnum left, FooEnum right)
    {
        var lv = (int) left;
        var rv = (int) right;
        return (rv % 1000 - lv % 1000) * 1000 + (rv - lv);
    }
}

原因

LINQとか、Enumとかをil2cppと組み合わせると死ぬときもあるみたいな話だと思うのですが、具体的にこれがダメだからということはわからず……。

公式マニュアルの「クロージャと匿名メソッド」の項に、

C#内のメソッド参照は全て参照型であり、したがってヒープに割り当てられるということです。
匿名メソッドをクロージャに変換すると、クロージャを受領するメソッドへ渡すために必要とされるメモリ量が大幅に増加します。

https://docs.unity3d.com/ja/current/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html

とあります(一部省略)。
この辺……?

特定に至るまで

特にエラーメッセージをはいてくれることもなく、UnityEditor上では元気に動いているのでしばらく原因がわかりませんでした。
粒度細かくビルドしていくのが大事ですよね。アジャイルアジャイル。
実機ではフリーズしてしまうだけで情報が得られなかったので、UnityCloudBuildではなく手元でXcodeを使ってビルドして、そこに出てくるログを読んだり、一時停止をすることで各スレッドがどこまで進んでいるのかを見てみました。
すでにC++のコードに変換されているものを読まないといけないのでよくわからなかったのですが、止まっている部分を見ると、ComparisonComparer<T> : IComparer<T>のような文字列が表示されていたので、自分の前回のビルドからの変更箇所と照らし合わせて特定しました。
結構時間かかったな……。