Version: 2017.2
Features currently not supported by Unity iOS
Сообщение об ошибках, приводящих к "падениям" на iOS

Решение проблем на iOS устройствах

There are some situations with iOS where your game can work perfectly in the Unity editor but then doesn’t work or maybe doesn’t even start on the actual device. The problems are often related to code or content quality. This section describes the most common scenarios.

Спустя некоторое время игра перестаёт отвечать. В строке состояния Xcode отображает “interrupted”.

Есть множество причин, почему это могло произойти. Обычно среди причин есть следующие:

  1. Ошибки скриптов, вроде неинициализированных переменных и т.п.
  2. Использование сторонних скомпилированных нативных библиотек Thumb. Такие библиотеки вызывают известную проблему в компоновщике iOS SDK и могут вызывать случайные крахи приложения.
  3. Использование универсальных (generic) типов со значимыми типами (value types) в качестве параметров (например List<int>, List<SomeStruct>, List<SomeEnum> и т.п.) для сериализуемых свойств скрипта.
  4. Использование рефлексии при установленном Stripping Level уровня Assembly и выше (находится в настройках проигрывателя).
  5. Errors in the native plugin interface (the managed code method signature does not match the native code function signature). Information from the XCode Debugger console can often help detect these problems (Xcode menu: View > Debug Area > Activate Console).

Консоль Xcode показывает “Program received signal: “SIGBUS” or EXC_BAD_ACCESS error”

This message typically appears on iOS devices when your application receives a NullReferenceException. There two ways to figure out where the fault happened:

Трассировки стека в управляемой среде

Unity includes software-based handling of the NullReferenceException. The AOT compiler includes quick checks for null references each time a method or variable is accessed on an object. This feature affects script performance which is why it is enabled only for development builds (enable the “script debugging” option in build settings dialog). If everything was done right and the fault actually is occurring in .NET code then you won’t see EXC_BAD_ACCESS anymore. Instead, the .NET exception text will be printed in the Xcode console (or else your code will just handle it in a “catch” statement). Typical output might be:

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 

Это означает, что ошибка произошла в методе handleTimeOfDay класса DayController, который работает как coroutine. И если это код скрипта, то тогда, в обычном случае, вам будет сообщён точный номер строки (например, “DayController.js:122”). Например, строка с ошибкой может выглядеть так:

 Instantiate(_imgwww.assetBundle.mainAsset);

Это могло произойти, если, допустим, скрипт пытается получить доступ к набору ассетов (asset bundle) не проверив, правильно ли он загрузился.

Трассировки стека в нативной среде

Трассировки стека в нативной среде - гораздо более мощный инструмент для исследования ошибок, но их использование требует некоторых навыков. Кроме того, обычно приложение уже не сможет работать после возникновения таких нативных (доступ к аппаратной памяти) ошибок. Чтобы получить трассировку нативного стека, введите bt all в консоли XCode Debugger. Тщательно исследуйте полученные следы стека - они могут содержать подсказки о том, где произошла ошибка. Вы можете увидеть что-то вроде такого:

...
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 ()
...

First of all you should find the stack trace for “Thread 1”, which is the main thread. The very first lines of the stack trace will point to the place where the error occurred. In this example, the trace indicates that the NullReferenceException happened inside the “OptionsMenu” script’s “Start” method. Looking carefully at this method implementation would reveal the cause of the problem. Typically, NullReferenceExceptions happen inside the Start method when incorrect assumptions are made about initialization order. In some cases only a partial stack trace is seen on the Debugger Console:

Thread 1 (thread 11523): 

1. 0 0x0062564c in start ()

Это означает, что нативные символы были разделены во время сборки приложения. Полную трассировку стека можно получить при помощи следующей процедуры:

  • Удалите приложение с устройства.
  • Очистите все targets.
  • Соберите и запустите.
  • Ещё раз получите трассировки стека указанным выше способом.

Когда к iOS приложению Unity подключена внешняя библиотека начинает происходить 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()” и после этого приложение сразу же падает

(Sometimes you might see a message like Program received signal: “0”.) This warning message is often not fatal and merely indicates that iOS is low on memory and is asking applications to free up some memory. Typically, background processes like Mail will free some memory and your application can continue to run. However, if your application continues to use memory or ask for more, the OS will eventually start killing applications and yours could be one of them. Apple does not document what memory usage is safe, but empirical observations show that applications using less than 50% MB of all device RAM (roughly 200–256 MB for 2nd generation ipad) do not have major memory usage problems. The main metric you should rely on is how much RAM your application uses. Your application memory usage consists of three major components:

  • код приложения (системе необходимо загрузить код вашего приложения в RAM и хранить его там, но часть может быть отброшена при особой необходимости)
  • нативная куча (используется движком для хранения в RAM его состояния, ваших ассетов и т.п.)
  • управляемая куча (используется средой выполнения Mono для хранения C# или JavaScript объектов)
  • GLES driver memory pools: textures, framebuffers, compiled shaders, etc. Your application memory usage can be tracked by two Xcode Instruments tools: Activity Monitor, Object Allocations and VM Tracker. You can start from the Xcode Run menu: Product > Profile and then select specific tool. Activity Monitor tool shows all process statistics including Real memory which can be regarded as the total amount of RAM used by your application. Note: OS and device HW version combination might noticeably affect memory usage numbers, so you should be careful when comparing numbers obtained on different devices.

Note: The internal profiler shows only the heap allocated by .NET scripts. Total memory usage can be determined via Xcode Instruments as shown above. This figure includes parts of the application binary, some standard framework buffers, Unity engine internal state buffers, the .NET runtime heap (number printed by internal profiler), GLES driver heap and some other miscellaneous stuff.

Другой инструмент показывает все выделения памяти сделанные вашим приложением, включая статистику как нативной, так и управляемой кучи (не забудьте проверить пункт Created and still living, чтобы узнать текущее состояние приложения). Важной статистикой является значение Net bytes.

Чтобы уменьшить потребление памяти:

  • Уменьшите размер финального файла приложения, используя самые строгие iOS stripping опции (функция расширенной лицензии) и избегайте ненужных зависимостей от разных .NET библиотек. См. разделы Настройки проигрывателя и Оптимизация размера собранного iOS проигрывателя для дополнительной информации.
  • Уменьшите размер вашего контента. Используйте PVRTC сжатие для текстур и используйте низкополигональные (low-poly) модели. См. уменьшение размера файла для дополнительной информации.
  • В скриптах не выделяйте больше памяти, чем надо. Следите за размером кучи Mono и её использованием при помощи строенного профайлера. *На заметку: Начиная с Unity 3.0 метод загрузки сцены существенно изменился и теперь все ассеты сцены предзагружаются. В результате стало меньше микропауз при создании игровых объектов. Если вам нужен более точный контроль загрузки и выгрузки ассетов во время игры, вам следует использовать Resources.Load и Object.Destroy.

Запрашивание объёма свободной памяти у системы может показаться хорошей идей для изучения производительности приложения. Однако, статистика свободной памяти является ненадёжной, т.к. система использует множество динамических буферов и кэшей. Единственным надёжным вариантом является отслеживание количества памяти, потребляемое вашим приложением, и использование этого в качестве главных показателей. Обратите внимание, как с течением времени меняются графики, произведённые описанными выше инструментами, особенно после загрузки новых уровней.

При запуске из Xcode игра работает корректно, но при запуске с устройства вручную, игра падает во время загрузки первого уровня.

Для этого может быть несколько причин. Вам нужно исследовать логи устройства для получения дополнительной информации. Соедините устройство с вашем Mac, запустите Xcode и выберите Window > Organizer из меню. Выберите ваше устройство на левой панели инструментов органайзера (Organizer), затем кликните на вкладку “Console” и внимательно изучите последние сообщения. Возможно вам понадобится исследовать отчёты о крахе. Как получить отчёты о крахе, вы можете выяснить здесь: http://developer.apple.com/iphone/library/technotes/tn2008/tn2151.html.

Консоль Xcode Organizer содержит сообщение “killed by SpringBoard”.

Существует плохо описанный лимит времени для iOS приложения, отведённый для отрисовки его первых кадров и обработки ввода. Если ваше приложение превысит этот лимит, SpringBoard его закроет. Это может случиться, например, если первая сцена в приложении слишком большая. Чтобы избежать подобной проблемы, рекомендуется создать маленькую стартовую сцену, которая просто отображает заставку, ждёт кадр-другой при помощи yield, а затем начинает загружать саму сцену. Это можно сделать с помощью очень простого кода, например:

function Start() {
    yield;
    Application.LoadLevel("Test");
}

Type.GetProperty() / Type.GetValue() вызывает падения приложения на устройстве

Currently Type.GetProperty() and Type.GetValue() are supported only for the .NET 2.0 Subset profile. You can select the .NET API compatibility level in the Player Settings.

На заметку: Type.GetProperty() и Type.GetValue() могут быть несовместимы со stripping’ом managed кода, что потребует их исключения (для этого во время stripping’а вы можете предоставить не подверженный ему пользовательский тип). Для дополнительной информации см. раздел Оптимизация размера собранного iOS проигрывателя.

Игра падает с ошибкой “ExecutionEngineException: Attempting to JIT compile method ‘SometType`1<SomeValueType>:.ctor ()’ while running with –aot-only.”

Реализация Mono .NET для iOS основана на технологии AOT (предварительная компиляция в нативный код), которая имеет свои ограничения. Она компилирует только те методы универсальных (generic) типов (где в качестве generic параметра используется value type), которые явно используются другим кодом. Если такие методы используются только через рефлексию или из нативного кода (например, в системе сериализации), то они будут пропущены во время AOT-компиляции. AOT-компилятору можно подсказать о том, что их надо включить в компиляцию разместив где-нибудь в теле скрипта вспомогательный метод. В нём можно обратиться к пропущенным методам, тем самым форсировав их AOT-компиляцию.

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

На заметку: value types - это базовые типы, структуры и перечисления.

На устройстве могут произойти различные падения при использовании комбинации из System.Security.Cryptography и stripping’а managed кода.

Сервисы .NET криптографии сильно полагаются на рефлексию и поэтому не совместимы со stripping’ом managed кода, т.к. рефлексия включает в себя статический анализ кода. Иногда самым простым решением падений является исключение всего пространства имён System.Security.Crypography из процесса stripping’а.

Процесс stripping’а может быть настроен добавлением пользовательского файла link.xml в папку Assets вашего проекта Unity. Он определяет какие типы и пространства имён следует исключить из stripping’а. Дополнительную информацию можно найти в разделе Оптимизация размера собранного iOS проигрывателя.

link.xml

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

Приложение падает при использовании System.Security.Cryptography.MD5 вместе со stripping’ом managed кода.

Вы можете воспользоваться описанным выше советом, или можете обойти эту проблему добавив дополнительную ссылку на определённый класс в коде вашего скрипта:

object obj = new MD5CryptoServiceProvider();

Ошибка среды выполнения “Ran out of trampolines of type 0/1/2”

Обычно эта ошибка происходит если вы используете много рекурсивных универсальных шаблонов (они же генерики, generics). Вы можете подсказать AOT-компилятору выделять больше трамплинов типа 0, типа 1 или типа 2. Дополнительные опции командной строки AOT-компилятора можно настроить в разделе “Other Settings” настроек проигрывателя. Для трамплинов типа 1, задайте nrgctx-trampolines=ABCD, где ABCD - число необходимых новых трамплинов (т.е. 4096). Для трамплинов типа 2 задайте nimt-trampolines=ABCD и для трамплинов типа 0 задайте ntrampolines=ABCD.

После обновления Xcode запуск Unity не удаётся и появляется сообщение “You are using Unity iPhone Basic. You are not allowed to remove the Unity splash screen from your game”

В некоторых последних релизах Xcode были введены изменения в PNG сжатие и инструмент оптимизации. Эти изменения могут вызвать ложные срабатывания проверок изменения заставочного экрана в среде выполнения Unity iOS. Если вы испытываете подобные проблемы, то попробуйте обновить Unity до последней публично доступной версии. Если это не помогло, то можете попробовать обойти проблему так:

  • Замените свой проект Xcode с нуля при сборке из Unity (вместо присоединения)

  • Удалите уже установленный проект с устройства

  • Очистите проект в Xcode (Product->Clean)

  • Clear Xcode’s Derived Data folders (Xcode->Preferences->Locations) If this still does not help try disabling PNG re-compression in Xcode:

  • Откройте свой проект Xcode

  • Выберите проект “Unity-iPhone”

  • Выберите вкладку “Build Settings”

  • Найдите опцию “Compress PNG file” и переключите в состояние NO

При отправке приложения в App Store происходит ошибка “iPhone/iPod Touch: application executable is missing a required architecture. At least one of the following architecture(s) must be present: armv6”

Такое сообщение вы можете получить когда обновляете уже существующее приложение, которое раньше было отправлено с поддержкой armv6. Unity 4.x и Xcode4.5 больше не поддерживают платформу armv6. Чтобы решить проблему отправки приложения просто смените в Unity Target OS Version (целевую версию системы) на 4.3 или выше.

WWW загрузки нормально работают в редакторе Unity и на Android, но не на iOS

Наиболее распространённой ошибкой является предположение, что процессы WWW скачивания всегда происходят в отдельном потоке. На некоторых платформах это может быть так, но вам не следует принимать это как должное. Лучший способ отследить статус WWW - либо использовать выражение yield, либо проверять статус в методе Update. Вам не следует использовать для этого циклы while.

При использовании Cocoa через нативную функцию, вызванную из скрипта, происходит ошибка “PlayerLoop called recursively!”.

Some operations with the UI will result in iOS redrawing the window immediately (the most common example is adding a UIView with a UIViewController to the main UIWindow). If you call a native function from a script, it will happen inside Unity’s PlayerLoop, resulting in PlayerLoop being called recursively. In such cases, you should consider using the performSelectorOnMainThread method with waitUntilDone set to false. It will inform iOS to schedule the operation to run between Unity’s PlayerLoop calls.

Profiler или Debugger не видит запущенную на iOS устройстве игру

  • Убедитесь, что вы собрали Development сборку и отметили флажки “Enable Script Debugging” и “Autoconnect profiler” (по мере необходимости).
  • Запущенное на устройстве приложение будет вести многоадресное вещание на 225.0.0.222 с UDP портом 54997. Убедитесь, что настройки вашей сети допускают этот трафик. Затем, профайлер наладит соединение с удалённым устройством по порту в диапазоне 55000–55511 для получения информации профайлера с устройства. Эти порты должны быть открыты для UDP доступа.

Отсутствующие DLL

If your application runs ok in editor but you get errors in your iOS project this may be caused by missing DLLs (e.g. I18N.dll, I19N.West.dll). In this case, try copying those dlls from within the Unity.app to your project’s Assets/Plugins folder. The location of the DLLs within the unity app is: Unity.app/Contents/Frameworks/Mono/lib/mono/unity You should then also check the stripping level of your project to ensure the classes in the DLLs aren’t being removed when the build is optimised. Refer to the iOS Optimisation Page for more information on iOS Stripping Levels.

В консоли Xcode Debugger такое сообщение: ExecutionEngineException: Attempting to JIT compile method ‘(wrapper native-to-managed) Test:TestFunc (int)’ while running with –aot-only

Обычно такое сообщение приходит, когда делегат managed функции передаётся в нативную функцию, но требуемый код враппера (обёртки) не был сгенерирован во время сборки приложения. Вы можете помочь 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));
    }

    // Use this for initialization
    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)”

Обычно эта ошибка значит что на один модуль приходится слишком много кода. В большинстве случаев это вызвано большими объёмами кода или включением больших .NET библиотек в сборку. А включение отладки скрипта может только ухудшить ситуацию, поскольку она добавляет несколько дополнительных инструкций в каждую функцию, так что с ней превысить лимит ещё проще.

Включение stripping’а managed кода в настройках проигрывателя может помочь с этой проблемой, особенно, если дело касается больших внешних .NET сборок. Но если проблема остаётся, то лучшим решением будет разделение пользовательского кода на несколько библиотек. Самым простой способ - переместить некоторую часть кода в папку Plugins. Расположенный там код будет помещён в отдельную библиотеку. Также изучите информацию о том, как имена особых папок влияют на компиляцию скрипта.

Features currently not supported by Unity iOS
Сообщение об ошибках, приводящих к "падениям" на iOS