Version: 5.3 (switch to 5.4b)
Features currently not supported by Unity iOS
Сообщение об ошибках, приводящих к "падениям" на iOS

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

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

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

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

  1. Ошибки скриптов, вроде неинициализированных переменных и т.п.
  2. Использование сторонних скомпилированных нативных библиотек Thumb. Такие библиотеки вызывают известную проблему в компоновщике iOS SDK и могут вызывать случайные крахи приложения.
  3. Использование универсальных (generic) типов со значимыми типами (value types) в качестве параметров (например List<int>, List<SomeStruct>, List<SomeEnum> и т.п.) для сериализуемых свойств скрипта.
  4. Использование рефлексии при установленном Stripping Level уровня Assembly и выше (находится в настройках проигрывателя).
  5. Ошибки в интерфейсе нативного плагина (подпись метода управляемого кода не совпадает с подписью функции нативного кода). Информация из консоли XCode Debugger часто может помочь определить эти проблемы (в меню Xcode: View > Debug Area > Activate Console).

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

Сообщение обычно высвечивается на iOS устройствах, когда ваше приложение принимает NullReferenceException. Есть 2 способа выяснить, где произошла ошибка:

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

Начиная с версии 3.4, в Unity существует программная обработка NullReferenceException. Компилятор AOT включает в себя быстрые проверки нулевых ссылок при каждом обращении к переменной или методу на объекте. Это функция влияет на производительность скрипта, в результате чего она включена только у сборок для разработчиков (пользователям базовой лицензии достаточно включить опцию “Development build” в диалоговом окне “Build Settings”, в то время как пользователям лицензии iOS Pro надо дополнительно включить опцию “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 

Это означает, что ошибка произошла в методе 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 ()
...

В первую очередь, вам следует найти трассировку стека “Thread 1”, то есть, главного потока. Самые первые строки трассировки стека укажут на место, где произошла ошибка. В данном примере, трассировка указывает, что NullReferenceException произошёл внутри скрипта “OptionsMenu” в методе “Start”. Если внимательно присмотреться к реализации метода, то можно найти причину проблемы. Обычно, NullReferenceException происходят внутри метода Start, в результате неверных предположений о порядке запуска. В некоторых случаях только часть трассировки стека видна в консоли Debugger’а:

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()” и после этого приложение сразу же падает

Иногда вы можете увидеть сообщение вроде Program received signal: “0”. Это предупреждение в большинстве случаев не фатально и просто означает, что iOS не хватает памяти и система просит приложения освободить немного памяти. Обычно, фоновые процессы, вроде Mail (Почта) освободят немного памяти и ваше приложение сможет продолжить работу. Тем не менее, если ваше приложение продолжит использовать память или попросит ещё больше, то система начнёт периодически закрывать приложения и ваше может быть одним из них. Apple не упомянули, какая нагрузка на память безопасна, но основываясь на опыте можно сказать, что приложения использующие меньше 50% от максимума (грубо говоря, где-то 200–256 МБ для iPad 2) не испытывают особых проблем с использованием памяти. Главный показатель, на который вы должны полагаться, это то, сколько RAM использует ваше приложение. Использование памяти вашим приложением состоит из трёх главных компонентов:

  • код приложения (системе необходимо загрузить код вашего приложения в RAM и хранить его там, но часть может быть отброшена при особой необходимости)
  • нативная куча (используется движком для хранения в RAM его состояния, ваших ассетов и т.п.)
  • управляемая куча (используется средой выполнения Mono для хранения C# или JavaScript объектов)
  • пулы памяти GLES-драйвера: текстуры, фреймбуферы, скомпилированные шейдеры и т.д. Использование памяти вашим приложением может быть отслежено тремя инструментами Xcode Instruments: Activity Monitor, Object Allocations и VM Tracker. Вы можете начать из меню Xcode Run: Product > Profile и затем выбрать определённый инструмент. Инструмент Activity Monitor отображает всю статистику процесса, включая Real memory, что можно считать как общее количество RAM используемое вашим приложением. На заметку: комбинация операционной системы системы и версии HW устройства может заметно повлиять на значения используемой памяти, так что внимательно сравнивайте значения полученные на разных устройствах.

На заметку: встроенный профайлер показывает только кучу, выделенную под .NET скрипты. Общее количество используемой памяти может быть определено при помощи Xcode Instruments, как было показано выше. Эта диаграмма включает части исполняемого файла приложения, несколько стандартных Framework-буферов, внутренние буферы состояний движка Unity, кучу среды выполнения .NET (число, выведенное внутренним профайлером), кучу GLES-драйвера и другую разнообразную информацию.

Другой инструмент показывает все выделения памяти сделанные вашим приложением, включая статистику как нативной, так и управляемой кучи (не забудьте проверить пункт 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() вызывает падения приложения на устройстве

На данный момент Type.GetProperty() и Type.GetValue() поддерживаются только профилем .NET 2.0 Subset. Вы можете выбрать уровень совместимости .NET API в 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)

  • Очистите папки производных данных Xcode (Xcode->Preferences->Locations) Если это всё равно не помогает, то попробуйте отключить PNG пережатие в 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!”.

Некоторые операции с интерфейсом приведут к немедленной перерисовке окна системой iOS (самый распространённый пример - добавление UIView вместе с UIViewController к главному UIWindow). Если вы вызовете нативную функцию из скрипта, это произойдёт внутри главного цикла проигрывателя (PlayerLoop) Unity, в результате чего PlayerLoop будет вызываться рекурсивно. В подобных случаях вам следует рассмотреть использование метода performSelectorOnMainThread с waitUntilDone = false. Это сообщит iOS, что надо поместить запуск операции между вызовами цикла проигрывателя Unity.

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

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

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

Если ваше приложение нормально работает в редакторе, но выдаёт ошибки в вашем iOS проекте, то это может быть вызвано отсутствующими библиотеками DLL (например, I18N.dll, I19N.West.dll). В таком случае попробуйте копировать эти DLL из Unity.app в папку Assets/Plugins вашего проекта. Расположение DLL библиотек внутри Unity.app: Unity.app/Contents/Frameworks/Mono/lib/mono/unity Вам также следует проверить уровень stripping’а вашего проекта, чтобы убедиться, что классы в DLL не были удалены при оптимизации сборки. Для большей информации касательно уровней iOS Stripping’а см. страницу по оптимизации проигрывателя iOS.

В консоли 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