Version: 2020.1
言語: 日本語
ソーシャル API
iOS のクラッシュバグのレポート方法

iOS デバイスのトラブルシューティング

iOS では、ゲームが Unity エディター上では完璧に動作するのに、その後、実際のデバイス上で動作しない、起動しないなどの状況が発生することがあります。問題は、多くの場合、コードやコンテンツの品質に関連してます。このセクションでは、もっとも一般的なシナリオについて説明します。

ゲームがしばらくして応答を停止し、Xcode がステータスバーに interrupted (中断) と表示する

これが起こる理由はいくつかあり、よくある原因は次のとおりです。

  • 初期化されていない変数を使うなどのスクリプトエラー。
  • サードパーティの Thumb でコンパイルしたネイティブライブラリ使用している。このようなライブラリは、iOS SDK のリンカーの既知の問題を誘発し、ランダムなクラッシュを引き起こす可能性があります。
  • シリアライズ可能なスクリプトプロパティに、パラメーターとして値型を持つジェネリック型 (例えば、List<int>、List<SomeStruct>、List<SomeEnum>) を使用している。
  • マネージコードストリッピングが有効になっているときリフレクションを使用している。
  • ネイティブプラグインインターフェースのエラー (マネージコードメソッドのシグネチャが、ネイティブコード関数のシグネチャと一致していない)。 Xcode のデバッガーコンソールからの情報は、多くの場合、これらの問題を検出するのに役立ちます (Xcode メニュー: View > Debug Area > Activate Console)。

Xcode コンソールが "Program received signal: SIGBUS” または EXC_BAD_ACCESS エラーを表示する

このメッセージは通常、アプリケーションが NullReferenceException を受信したときに iOS デバイスに表示されます。障害が発生した場所を特定するには 2 つの方法があります。

マネージスタックトレース

Unity には NullReferenceException のソフトウェアベースの処理が備わっています。AOT コンパイラーはメソッドや変数がオブジェクト上でアクセスされるたびに、null 参照のクイックチェックを行います。この機能はスクリプトのパフォーマンスに影響します。そのため、開発ビルドでのみ使用可能です (Build Settings ダイアログの script debugging オプションを有効にします)。すべてが正しく行われ、実際に障害が .NET コードで発生している場合は、EXC_BAD_ACCESS は表示されなくなります。代わりに、.NET 例外テキストが Xcode コンソールに表示されます (そうでない場合は、コードは単に catch 文でそれを処理します)。典型的な出力は以下のとおりです。

Unhandled Exception: System.NullReferenceException: A null value was found where an object instance was required.
  at DayController+$handleTimeOfDay$121+$.MoveNext () [0x0035a] in DayController.js:122

これは、障害が DayController クラスのコルーチンとして動作している handleTimeOfDay メソッドで起こったことを示しています。それがスクリプトコードであれば、通常、正確な行番号を通知します (例えば、“DayController.js:122”)。問題のある行は、以下のようなものが考えられます。

 Instantiate(_imgwww.assetBundle.mainAsset);

これは例えば、スクリプトが最初にそれが正しくダウンロードされたことを確認せずにアセットバンドルにアクセスした場合に発生する可能性があります。

ネイティブスタックトレース

ネイティブスタックトレースは、障害調査のためのより強力なツールですが、使用する際、いくつかの専門知識を必要とします。さらに一般的にネイティブエラー(ハードウェアメモリアクセス)が起きた後には続行できません。ネイティブスタックトレースを取得するには、Xcode のデバッガーコンソールに bt all と入力します。出力されたスタックトレースを慎重に調べると、エラーが発生した場所についてのヒントを含んでいることがあります。例えば、次のように表示されます。

...
Thread 1 (thread 11523): 

1. 0 0x006267d0 in m_OptionsMenu_Start ()
1. 1 0x002e4160 in wrapper_runtime_invoke_object_runtime_invoke_void__this___object_intptr_intptr_intptr ()
1. 2 0x00a1dd64 in mono_jit_runtime_invoke (method=0x18b63bc, obj=0x5d10cb0, params=0x0, exc=0x2fffdd34) at /Users/mantasp/work/unity/unity-mono/External/Mono/mono/mono/mini/mini.c:4487
1. 3 0x0088481c in MonoBehaviour::InvokeMethodOrCoroutineChecked ()
...

最初に、メインスレッドである Thread 1 のスタックトレースを見つける必要があります。スタックトレースの最初の行は、エラーが発生した場所を指しています。この例では、トレースは NullReferenceException が OptionsMenu スクリプトの Start メソッド内で起こったことを示しています。このメソッドの実装を注意深く見ると、問題の原因が明らかになります。一般的に、NullReferenceExceptions は、初期化の順序について間違った前提が行われる際に Start メソッドの内部で起きます。 部分的なスタックトレースだけがデバッガーコンソールに表示されるケースもあります。

Thread 1 (thread 11523): 

1. 0 0x0062564c in start ()

これはネイティブのシンボルがアプリケーションのリリースビルドの際に取り除かれたことを示しています。フルスタックトレースは、以下の手順で得ることができます。

  • デバイスからアプリケーションを削除します。
  • すべてのターゲットをクリーンアップします。
  • ビルドおよび実行します。
  • スタックトレースを上記の手順どおりに取得します。

外部ライブラリが Unity iOS アプリケーションにリンクされているとき EXC_BAD_ ACCESS が発生する

通常、外部ライブラリは、ARM Thumb 命令セットでコンパイルされたときに生成されます。現在、このようなライブラリは Unity と互換性がありません。Thumb 命令なしでライブラリを再コンパイルすることで、簡単に問題を解決することができます。Xcode プロジェクトのライブラリからは以下の手順でこれを行います。

  • Xcode でメニューから View > Navigators > Show Project Navigator を選びます。
  • Unity-iPhone プロジェクトを選択し、Build Settings タブをアクティブにします
  • 検索フィールドに Other C Flags と入力します
  • そこに -mno-thumb フラグを加え、ライブラリを再ビルドします。

ライブラリのソースが利用できない場合は、Thumb コードではないライブラリのバージョンをサプライヤーに依頼する必要があります。

Xcode のコンソールに “WARNING -> applicationDidReceiveMemoryWarning()” が表示され、その後すぐにアプリケーションがクラッシュする

場合によっては、Program received signal: “0” のようなメッセージが表示されることもあります。 この警告メッセージは多くの場合は致命的ではなく、iOS のメモリが不足していることを示しており、メモリを一部解放するようアプリケーションに要求していることを示しています。通常、メールのようなバックグラウンドプロセスはメモリの一部を解放し、アプリケーションは引き続き実行できます。ただし、アプリケーションが引き続きメモリを使用したり、それ以上のメモリを要求する場合、OS は最終的にアプリケーションを強制終了し、その中に作業中のものが含まれてしまう場合もあります。Apple はどのメモリの使用が安全であるかを明文化していませんが、経験から、デバイスの全 RAM の 50% 未満をアプリケーションが使用している場合、メモリ使用量の問題はほとんどありません。 主に参考にする基準は、アプリケーションが使用する RAM の量です。アプリケーションのメモリ使用量は、以下の 3 つの主要コンポーネントから成ります。

  • アプリケーションコード (OS が RAM にアプリケーションコードを読み込み、保持する必要があります。しかし、実際には必要な場合は一部が廃棄される場合があります)
  • ネイティブヒープ (状態、アセットなどを RAM に格納するためにエンジンによって使用されます)
  • マネージヒープ (C# オブジェクトを保持するために Mono ランタイムによって使用されます)
  • GLES ドライバーのメモリプール: テクスチャ、フレームバッファ、コンパイル済みシェーダーなど。 アプリケーションのメモリ使用量は Xcode Instruments の 2 つのツール、Activity MonitorAllocations で追跡できます。 Xcode の Run メニューから Product > Profile を選択し、特定のツールを選択するか、Xcode > Open Developer Tools > Instruments から開きます。Activity Monitor ツールは、Real memory を含むすべてのプロセスの統計情報を表示します。この統計情報は、アプリケーションが使用中の RAM の総量とみなされます。注意 OS とデバイスの HW バージョンの組み合わせは、メモリ使用量に著しい影響を与える場合があります。そのため、異なるデバイスで取得した数量と比較するときは注意が必要です。

注意: ビルトインプロファイラーでのパフォーマンス測定は、.NET スクリプトによって割り当てられたヒープのみを表示します。総メモリ使用量は、上記のように Xcode Instruments を介して決定できます。この図では、アプリケーションバイナリの一部、いくつかの標準フレームワークバッファ、Unity エンジン内部ステートバッファ、.NET ランタイムヒープ(内部プロファイラーによって表示される番号)、GLES ドライバヒープ、いくつかの他のさまざまなものを含みます。

もう 1 つのツールは、アプリケーションによって行われたすべての割り当てを表示し、ネイティブヒープとマネージヒープ統計の両方を含んでいます。重要な統計情報は Net bytes 値です。

メモリ使用を抑える方法は以下の通りです。

  • iOS のもっとも強力なストリッピングオプションを使用して、アプリケーションのバイナリサイズを小さくし、さまざまな .NET ライブラリの不要な依存関係を避けるようにします。詳細は、Player 設定ビルドした iOS プレイヤーのサイズ最適化 を参照してください。
  • コンテンツのサイズを小さくしてください。テクスチャに PVRTC 圧縮を使用し、ローポリゴンモデルを使います。詳細は、ファイルサイズの削減 を参照してください。
  • スクリプトに必要以上にメモリを割り当てないでください。 内部プロファイラー を使用してモノヒープサイズと使用状況を追跡します。

空きメモリの量について OS に照会することは、アプリケーションのパフォーマンスを評価するためのよいアイデアのように思えるかもしれません。ただし、OS はダイナミックバッファとキャッシュを多く使用してるので、空きメモリの統計は信頼できない傾向があります。唯一、信頼できるアプローチは、アプリケーションのメモリ消費量を追跡し、これを主な指標として使うことです。特に新しいレベルを読み込んだとき、上記のツールでどのようにグラフが時間の経過とともに変化するかに注意を払います。

ゲームが Xcode から起動したときには正しく実行されるが、デバイス上で手動で起動すると最初のレベルのロード中にクラッシュする

これにはいくつかの理由が考えられます。より多く詳細を確認するために、デバイスのログをチェックする必要があります。Mac にデバイスを接続し、Xcode を起動してメニューから Window > Devices and Simulators を選択します。ウィンドウの左ツールバーで使っているデバイスを選択し、Show the device console ボタンをクリックして慎重に最新のメッセージを確認します。さらにクラッシュレポートを検証する必要があるかもしれません。クラッシュレポートを取得するには、以下を参照してください。http://developer.apple.com/iphone/library/technotes/tn2008/tn2151.html

Xcode Organizer コンソールのメッセージに “killed by SpringBoard” が含まれる

iOS アプリケーションが最初のフレームのレンダリングと入力処理を行うために、報告が不十分な時間制限があります。アプリケーションがこの制限を超えると SpringBoard に強制終了されます。例えば、アプリケーションの最初のシーンが大きすぎると発生する場合があります。この問題を避けるには、スプラッシュスクリーンを表示するだけの小さな初期のシーンを作成し、 yield で 1 、 2 フレーム待ってから、実際のシーンをロードすることをお勧めします。これは以下のような簡単なコードで実行できます。

IEnumerator Start() {
    yield return new WaitForEndOfFrame();
// UnityEngine.SceneManagement ディレクティブの使用をわすれないでください
    SceneManager.LoadScene("Test");
}

Type.GetProperty() または Type.GetValue() によってデバイスのクラッシュが発生する

現在、Type.GetProperty()Type.GetValue().NET 2.0 Subset プロファイルでのみサポートされています。.NET API の互換性レベルは、Player 設定で選択できます。

注意: Type.GetProperty()Type.GetValue() は、マネージコードストリッピングと互換性がない可能性があり、除外する必要があるかもしれません(これを達成するためにストリッピング処理中にカスタムの非ストリッピングタイプのリストを提供して除外することができます)。より詳しいことは ビルドした iOS プレイヤーのサイズ最適化を参照してください。

エラーメッセージ “ExecutionEngineException: Attempting to JIT compile method ‘SometType`1<SomeValueType>:.ctor ()’ while running with –aot-only.” を表示してゲームがクラッシュする

iOS 用の Mono .NET の実装は、AOT (ネイティブコードへの事前コンパイル) 技術に基づいていますが、その制限があります。それは、他のコードによって明示的に使用されるジェネリック型のメソッド (ジェネリックパラメーターとして値型が使用される) のみをコンパイルします。このようなメソッドがリフレクションを通してのみ、または、ネイティブコード(すなわちシリアライゼーションシステム) からのみ使用される場合、それらは AOT コンパイル中にスキップされます。AOT コンパイラーは、スクリプトコードのどこかにダミーのメソッドを追加することで、コードを加えることが暗示されます。これは、欠けているメソッドを参照するので、それらを事前にコンパイルします。

void _unusedMethod() {
    var tmp = new SomeType<SomeValueType>();
}

注意 値型は基本型、enums、structs です。

System.Security.Cryptography とマネージコードストリッピングの組み合わせを使用するとき、さまざまなクラッシュがデバイス上で発生する

.NET の暗号化サービスはリフレクション に大きく依存しているため、マネージコードストリッピングと互換性がありません。なぜなら、これが静的コード分析を含むからです。クラッシュのもっとも簡単な解決法は、ストリッピングプロセスから System.Security.Cryptography 名前空間全体を取り除くことです。

ストリッピングプロセスは、カスタムの link.xml ファイルを Unity プロジェクトの Assets フォルダーに加えてカスタマイズできます。これは、どの型と名前空間をストリッピングから除外するかを指定します。詳細は iOS プレイヤーサイズの最適化ガイド を参照してください。

link.xml

<linker>
       <assembly fullname="mscorlib">
               <namespace fullname="System.Security.Cryptography" preserve="all"/>
       </assembly>
</linker>

マネージコードストリッピングと共に System.Security.Cryptography.MD5 を使うとアプリケーションがクラッシュする

上記のアドバイスを検討するか、特定のクラスへの追加の参照をスクリプトコードに加えることによって、この問題を回避してください。

object obj = new MD5CryptoServiceProvider();

“Ran out of trampolines of type 0/1/2” ランタイムエラー

通常、再帰的なジェネリックを多く使用する場合に、このエラーは発生します。より多くの type0、type1、type2 トランポリンを割り当てるよう AOT コンパイラへ指示できます。追加の AOT コンパイラーのコマンドラインオプションは Player 設定Other Settings セクションで指定できます。type 0 トランポリンの場合、 ntrampolines=ABCD を指定、つまり ABCD は必須の新しいトランポリンの数 (すなわち 4096) を指定します。type 1 トランポリンは nrgctx-trampolines=ABCD を指定し、type 2 トランポリンは nimt-trampolines=ABCD を指定します。

Xcode Unity iOS をアップグレードした後、ランタイムエラーメッセージ “You are using Unity iPhone Basic. You are not allowed to remove the Unity splash screen from your game” (Unity iPhone Basic を使用しています。ゲームから Unity のスプラッシュスクリーンを削除することはできません) が表示される

最新の Xcode リリースでは、PNG 圧縮と最適化ツールに変更が加えられました。これらの変更は、スプラッシュスクリーンの変更に関する Unity iOS ランタイムチェックで誤検出される原因になる場合があります。このような問題が発生した場合は、Unity を一般公開されている最新のバージョンにアップグレードしてみてください。これで問題が解決しない場合は、以下の回避策を検討してください。

  • Unity からビルドするとき (それを追加するのではなく) Xcode プロジェクトをゼロから置き換えてビルドします。
  • すでにインストールされているプロジェクトをデバイスから削除します。
  • Xcode のプロジェクトをクリーンアップします (Product> Clean)。
  • Xcode の Derived Data フォルダーを消去します (Xcode> Preferences> Locations)。

これでもまだ解決しない場合は Xcode の PNG の再圧縮を無効にしてみてください。

  • Xcode プロジェクトを開きます。
  • Unity-iPhone プロジェクトを選択します。
  • Build Settings タブを選択します。
  • Compress PNG files オプションを探し、NO に設定します。

WWW のダウンロードが Unity Editor と Android 上では正常に動作するが、iOS 上ではうまくいかない

よくある間違いは、WWW のダウンロードが常に別のスレッドで起きていることを前提にしていることです。一部のプラットフォームでは正しいかもしれませんが、当然のこととするべきではありません。WWW の状態を追跡するための最良の方法は、yield ステートメントを使用するか、または Update メソッド でステータスを確認するかのどちらかです。ビジーになる while ループは使うべきではありません。

スクリプトからコールしたネイティブ関数を経由して Cocoa を使用した際に “PlayerLoop called recursively!” (PlayerLoop が再帰的に呼ばれてます) エラーが発生する

UI のいくつかの操作は iOS に即座にウィンドウを再描画させます (もっとも一般的な例は、メインの UIWindow へ UIViewController を伴う UIView を追加する場合)。スクリプトからネイティブ関数を呼び出す場合、それは Unity の PlayerLoop 内部で発生し、結果的に PlayerLoop が再帰的に呼び出されることになります。その場合、performSelectorOnMainThread で waitUntilDone を false に設定して使用する必要があります。これにより、Unity の PlayerLoop の呼び出しの間に実行する処理のスケジュールを iOS に通知します。

プロファイラーやデバッガーで iOS デバイス上で実行しているゲームを見ることができない

  • 開発ビルドを作成していることを確認し、Script DebuggingAutoconnect Profiler ボックスをチェックします (必要に応じて)。
  • デバイス上で動作するアプリケーションは、UDP ポート 54997 の 225.0.0.222 に マルチキャストやブロードキャストを行います。使用するネットワーク設定がこのトラフィックを許可することを確認してださい。次に、プロファイラーはリモートデバイスのポート番号 55000 - 55511 の範囲に接続し、デバイスからプロファイラーデータをフェッチします。これらのポートは TCP アクセスのために開いている必要があります。

DLL が見つからない

アプリケーションがエディターで正常に実行するにもかかわらず、iOS プロジェクトでエラーが発生する場合は、DLL (例えば、I18N.dll、I19N.West.dll) が見つからないことが原因かもしれません。このような場合、それらの DLL を Unity.app からプロジェクトの Assets\Plugins フォルダーにコピーしてみてください。Unity アプリケーション内の DLL の場所は以下の通りです。 Unity.app\Contents\Frameworks\Mono\lib\mono\unity 次に、プロジェクトのストリッピングレベルをチェックして、ビルドの最適化時に DLL のクラスが削除されていないことを確認する必要があります。iOS ストリッピングレベルの詳細は、iOS ストリッピングレベル を参照してください。

Xcode デバッガーコンソールが –aot-only で実行時に以下を出力する: ExecutionEngineException: Attempting to JIT compile method ‘(wrapper native-to-managed) Test:TestFunc (int)’

通常、このようなメッセージはマネージ関数デリゲートがネイティブ関数に渡されたときに受信されますが、アプリケーションのビルド時に必要なラッパーコードは生成されません。どのメソッドがデリゲートとしてネイティブコードに渡されるかを暗示して AOT コンパイラーを助けることができます。これを行うには MonoPInvokeCallbackAttribute カスタム属性を加えます。現在、静的メソッドのみがデリゲートとしてネイティブコードに渡されます。

コードサンプル

using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;
using AOT;

public class NewBehaviourScript : MonoBehaviour {
    [DllImport ("__Internal")]
    private static extern void DoSomething (NoParamDelegate del1, StringParamDelegate del2);

    delegate void NoParamDelegate ();
    delegate void StringParamDelegate (string str);
    
    [MonoPInvokeCallback(typeof(NoParamDelegate))]
    public static void NoParamCallback() {
        Debug.Log ("Hello from NoParamCallback");
    }
    
    [MonoPInvokeCallback(typeof(StringParamDelegate))]
    public static void StringParamCallback(string str) {
        Debug.Log(string.Format("Hello from StringParamCallback {0}", str));
    }

    // 初期化にこれを使用
    void Start() {
        DoSomething(NoParamCallback, StringParamCallback);
    }
}

Xcode が以下のコンパイルエラーを表示: “ld : unable to insert branch island. No insertion point available. for architecture armv7”, “clang: error: linker command failed with exit code 1 (use -v to see invocation)”

このエラーは通常、1 つのモジュールにあまりにも多くのコードがあることを意味します。一般的に、多くのスクリプトコードがあるか、大きな外部 .NET アセンブリをビルドに含めることによって発生します。また、スクリプトのデバッグを有効にすると、各関数にかなり多くの命令を追加し、制限に達しやすくなるため、状況を悪化させる可能性があります。

Player 設定でマネージコードのストリッピングを有効にすると、特に大きな外部 .NET アセンブリが含まれている場合に、この問題の解決に役立ちます。しかし、問題が解決しない場合は、ユーザースクリプトのコードを複数のアセンブリに分割することをお勧めします。これを行う最も簡単な方法は、コードの一部を Plugins フォルダーに移動することです。この場所のコードは別のアセンブリに置かれます。また、特別なフォルダー名 がスクリプトのコンパイルに与える影響に関する情報も確認してください。


  • 2018–06–14 修正されたページ
ソーシャル API
iOS のクラッシュバグのレポート方法