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.
Есть множество причин, почему это могло произойти. Обычно среди причин есть следующие:
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 ()
Это означает, что нативные символы были разделены во время сборки приложения. Полную трассировку стека можно получить при помощи следующей процедуры:
Обычно это происходит, когда внешняя библиотека скомпилирована с набором команд ARM Thumb. На данный момент, такие библиотеки не совместимы с Unity. Проблему можно легко решить, заново скомпилировав библиотеку без Thumb-команд. Вы можете это сделать для Xcode проекта библиотеки при помощи следующих шагов:
Если исходный код библиотеки не доступен, то вам следует попросить у поставщика/разработчика версию библиотеки без Thumb-команд.
(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:
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.
Чтобы уменьшить потребление памяти:
Запрашивание объёма свободной памяти у системы может показаться хорошей идей для изучения производительности приложения. Однако, статистика свободной памяти является ненадёжной, т.к. система использует множество динамических буферов и кэшей. Единственным надёжным вариантом является отслеживание количества памяти, потребляемое вашим приложением, и использование этого в качестве главных показателей. Обратите внимание, как с течением времени меняются графики, произведённые описанными выше инструментами, особенно после загрузки новых уровней.
Для этого может быть несколько причин. Вам нужно исследовать логи устройства для получения дополнительной информации. Соедините устройство с вашем Mac, запустите Xcode и выберите Window > Organizer из меню. Выберите ваше устройство на левой панели инструментов органайзера (Organizer), затем кликните на вкладку “Console” и внимательно изучите последние сообщения. Возможно вам понадобится исследовать отчёты о крахе. Как получить отчёты о крахе, вы можете выяснить здесь: http://developer.apple.com/iphone/library/technotes/tn2008/tn2151.html.
Существует плохо описанный лимит времени для iOS приложения, отведённый для отрисовки его первых кадров и обработки ввода. Если ваше приложение превысит этот лимит, SpringBoard его закроет. Это может случиться, например, если первая сцена в приложении слишком большая. Чтобы избежать подобной проблемы, рекомендуется создать маленькую стартовую сцену, которая просто отображает заставку, ждёт кадр-другой при помощи yield, а затем начинает загружать саму сцену. Это можно сделать с помощью очень простого кода, например:
function Start() {
yield;
Application.LoadLevel("Test");
}
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 проигрывателя.
Реализация Mono .NET для iOS основана на технологии AOT (предварительная компиляция в нативный код), которая имеет свои ограничения. Она компилирует только те методы универсальных (generic) типов (где в качестве generic параметра используется value type), которые явно используются другим кодом. Если такие методы используются только через рефлексию или из нативного кода (например, в системе сериализации), то они будут пропущены во время AOT-компиляции. AOT-компилятору можно подсказать о том, что их надо включить в компиляцию разместив где-нибудь в теле скрипта вспомогательный метод. В нём можно обратиться к пропущенным методам, тем самым форсировав их AOT-компиляцию.
void _unusedMethod() {
var tmp = new SomeType<SomeValueType>();
}
На заметку: value types - это базовые типы, структуры и перечисления.
Сервисы .NET криптографии сильно полагаются на рефлексию и поэтому не совместимы со stripping’ом managed кода, т.к. рефлексия включает в себя статический анализ кода. Иногда самым простым решением падений является исключение всего пространства имён System.Security.Crypography из процесса stripping’а.
Процесс stripping’а может быть настроен добавлением пользовательского файла link.xml в папку Assets вашего проекта Unity. Он определяет какие типы и пространства имён следует исключить из stripping’а. Дополнительную информацию можно найти в разделе Оптимизация размера собранного iOS проигрывателя.
<linker>
<assembly fullname="mscorlib">
<namespace fullname="System.Security.Cryptography" preserve="all"/>
</assembly>
</linker>
Вы можете воспользоваться описанным выше советом, или можете обойти эту проблему добавив дополнительную ссылку на определённый класс в коде вашего скрипта:
object obj = new MD5CryptoServiceProvider();
Обычно эта ошибка происходит если вы используете много рекурсивных универсальных шаблонов (они же генерики, generics). Вы можете подсказать AOT-компилятору выделять больше трамплинов типа 0, типа 1 или типа 2. Дополнительные опции командной строки AOT-компилятора можно настроить в разделе “Other Settings” настроек проигрывателя. Для трамплинов типа 1, задайте nrgctx-trampolines=ABCD, где ABCD - число необходимых новых трамплинов (т.е. 4096). Для трамплинов типа 2 задайте nimt-trampolines=ABCD и для трамплинов типа 0 задайте ntrampolines=ABCD.
В некоторых последних релизах 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
Такое сообщение вы можете получить когда обновляете уже существующее приложение, которое раньше было отправлено с поддержкой armv6. Unity 4.x и Xcode4.5 больше не поддерживают платформу armv6. Чтобы решить проблему отправки приложения просто смените в Unity Target OS Version (целевую версию системы) на 4.3 или выше.
Наиболее распространённой ошибкой является предположение, что процессы WWW скачивания всегда происходят в отдельном потоке. На некоторых платформах это может быть так, но вам не следует принимать это как должное. Лучший способ отследить статус WWW - либо использовать выражение yield, либо проверять статус в методе Update. Вам не следует использовать для этого циклы while.
Некоторые операции с интерфейсом приведут к немедленной перерисовке окна системой iOS (самый распространённый пример - добавление UIView вместе с UIViewController к главному UIWindow). Если вы вызовете нативную функцию из скрипта, это произойдёт внутри главного цикла проигрывателя (PlayerLoop) Unity, в результате чего PlayerLoop будет вызываться рекурсивно. В подобных случаях вам следует рассмотреть использование метода performSelectorOnMainThread с waitUntilDone = false. Это сообщит iOS, что надо поместить запуск операции между вызовами цикла проигрывателя Unity.
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.
Обычно такое сообщение приходит, когда делегат 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);
}
}
Обычно эта ошибка значит что на один модуль приходится слишком много кода. В большинстве случаев это вызвано большими объёмами кода или включением больших .NET библиотек в сборку. А включение отладки скрипта может только ухудшить ситуацию, поскольку она добавляет несколько дополнительных инструкций в каждую функцию, так что с ней превысить лимит ещё проще.
Включение stripping’а managed кода в настройках проигрывателя может помочь с этой проблемой, особенно, если дело касается больших внешних .NET сборок. Но если проблема остаётся, то лучшим решением будет разделение пользовательского кода на несколько библиотек. Самым простой способ - переместить некоторую часть кода в папку Plugins. Расположенный там код будет помещён в отдельную библиотеку. Также изучите информацию о том, как имена особых папок влияют на компиляцию скрипта.