使用以下信息有助于找到相应的解决方案应对在 iOS 设备上运行 Unity 应用程序时的常见崩溃和其他问题。
注意:如果在故障排除后问题仍然存在,请参阅报告 iOS 崩溃错误。
以下列表提供了此问题的一些常见原因:
List<int>、List<SomeStruct>、List<SomeEnum>。来自 Xcode 调试器控制台的信息通常有助于检测这些问题。从查看 (View) > ** 调试区 (Debug Area)** > 激活控制台 (Activate Console) 访问调试器控制台。
在 iOS 设备上,当应用程序收到 NullReferenceException 时,通常会出现此消息。使用本机堆栈跟踪来查找故障发生的位置。
原生堆栈跟踪是一种实用的故障调查工具,但使用时需要一些专门知识。在这些硬件内存访问错误发生后,通常无法继续。要获取原生堆栈跟踪,请在 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_ 方法中。检查此方法实现情况有助于揭示问题的原因。通常,如果对初始化顺序做出不正确的判断,_Start_ 中就会发生 NullReferenceExceptions。
调试器控制台上有时仅显示部分堆栈跟踪。例如:
Thread 1 (thread 11523):
1. 0 0x0062564c in start ()
此消息表明原生符号在应用程序的发行版构建过程中被剥离。要进一步调查,请使用以下步骤访问完整的堆栈跟踪:
您可能会遇到类似 Program received signal: "0" 的警告消息。此警告消息通常并不致命,表示 iOS 内存不足。通常情况下,像邮件这类后台进程会释放一些内存,这样你的应用程序就能继续运行。但是,如果应用程序继续使用内存或请求更多内存,iOS 将开始终止包括您自己的应用程序在内的应用程序。Apple 没有公布多大的内存使用量是安全的,但经验观察表明,使用不到 50% 的整体设备 RAM 的应用程序不会遇到严重的内存使用量问题。
主要指标是应用程序使用的 RAM 大小。应用程序内存使用量由以下部分构成:
| Component | 描述 |
|---|---|
| 应用程序代码 | 操作系统需要在 RAM 中加载和保留应用程序代码,但如果确实需要,可能会丢弃其中一些代码。 |
| 原生堆 | 由引擎用于在 RAM 中存储其状态、资源。 |
| 托管堆 | 由__ il2cpp__种由 Unity 开发的脚本后端,可在为某些平台构建项目时替代 Mono。更多信息 See in Glossary 运行时用于存储 C# 对象。 |
| 金属内存池 | 用于存储纹理、帧缓冲区和编译的着色器。 |
您可以在 Xcode 中跟踪应用程序内存使用情况。有关更多信息,请参阅收集有关内存使用的信息(Apple 开发者)。
要保持较低的内存使用量,请使用以下建议:
向操作系统查询可用内存量看似是评估应用性能的最有效方式。然而,由于操作系统使用了大量的动态缓冲区和缓存,可用内存的统计数据很可能是不可靠的。建议跟踪应用程序的内存消耗情况,并将此作为主要指标,与 Xcode 内存工具结合使用,尤其是在加载新场景后。
建议检查设备日志以获得更多详细信息。要完成此操作,请使用以下步骤:
您可能还需要调查崩溃报告。有关更多信息,请参阅获取崩溃报告和诊断日志 (Apple Developer)。
iOS 应用程序在渲染第一帧画面和处理输入时,有一个设定的时间限制。如果应用程序超过此限制,SpringBoard 会将其终止。例如,当应用程序中第一个场景的规模过大时可能会发生这种情况。建议创建一个带有启动画面的小型初始场景,等待几帧,然后继续加载更大的场景。要实现此目的,请使用以下示例:
IEnumerator Start() {
yield return new WaitForEndOfFrame();
// Do not forget using UnityEngine.SceneManagement directive
SceneManager.LoadScene("Test");
}
.NET 加密服务与托管代码剥离不兼容。这些服务依赖于反射,而托管代码剥离涉及静态代码分析。通过向 Unity 项目的 Assets 文件夹中添加自定义的 link.xml 文件可以自定义剥离过程。指定要从剥离中排除的类型和命名空间。从剥离过程中排除 System.Security.Crypography 命名空间以帮助解决此问题。例如,向 link.xml 文件添加以下内容:
<linker>
<assembly fullname="mscorlib">
<namespace fullname="System.Security.Cryptography" preserve="all"/>
</assembly>
</linker>
可以按照上一部分所述的相同方式解决此问题,也可以在脚本代码中添加对特定类的额外引用。要完成此操作,请使用以下示例:
object obj = new MD5CryptoServiceProvider();
UI 中的某些操作会导致 iOS 立即重绘窗口。最常见的示例是,将具有 UIViewController 的 UIView 添加到主 UIWindow。如果从脚本中调用一个原生函数,那么这个调用会发生在 Unity 的 PlayerLoop 内部,从而导致 PlayerLoop 被递归调用。发生这种情况时,您将收到错误消息 PlayerLoop called recursively!。在这种情况下,请考虑使用 performSelectorOnMainThread (Apple Developer) 方法,并将 waitUntilDone 设置为 false。它会通知 iOS 将该操作安排在 Unity 的 PlayerLoop 调用之间运行。
要诊断此问题,请使用以下建议:
54997 上向 225.0.0.222 进行多播广播。检查你的网络设置是否允许此类网络流量通过。性能分析器将连接到远程设备上 55000 - 55511 范围内的端口,以便从设备中获取性能分析器数据。这些端口需要开放以便进行 TCP 访问。
当构建的机器代码太大并且已达到 Xcode 限制时,会导致此问题。如果您有很多脚本代码,或者在构建中使用大型外部 .NET 程序集,则会发生这种情况。使用脚本调试 (Script Debugging) 构建设置也会增加此问题,因为它为每个函数创建额外的指令。
要解决此问题,请在 Unity 编辑器中导航到编辑 (Edit) > 项目设置 (Project Settings) > 播放器 (Player) > iOS,然后尝试以下一个或多个选项:
如果问题仍然存在,建议将用户脚本代码拆分为多个程序集。例如,Plugins 文件夹可用于放置任何拆分代码,因为此位置的代码会添加到不同的程序集。有关特殊文件夹名称如何影响脚本编译的信息,建议参阅特殊文件夹和脚本编译顺序。
从 Xcode 14.3 版开始,Apple 引入了目标架构 (Destination Architecture) 选项。借助目标架构 (Destination Architecture),您可以在基于 ARM 的 Mac 上使用 iOS 模拟器,而无需在 Rosetta 模拟器模式下运行 Xcode。
要查看 iOS 模拟器,请使用以下步骤: