http://7dot9.com/?p=444ios
http://whydoidoit.com/2012/08/20/unity-serializer-mono-and-trampolines/app
肯定具体缘由async
那么好吧,打一个测试版本再来看,而后再等着崩溃,查看崩溃日志吧,最终看到的崩溃日志中,崩溃线程输出信息以下:ide
Thread 27 Crashed:函数
0 libsystem_kernel.dylib 0x38e671fc __pthread_kill + 8性能
1 libsystem_pthread.dylib 0x38ecea4e pthread_kill + 54测试
2 libsystem_c.dylib 0x38e18028 abort + 72优化
3 gowonline 0x0178a0c0 mono_handle_native_sigsegv + 312this
4 gowonline 0x01779a30 mono_sigsegv_signal_handler + 256编码
5 libsystem_platform.dylib 0x38ec9720 _sigtramp + 40
6 gowonline 0x00114f48 m_RestSharp_Http_ExecuteCallback_RestSharp_HttpResponse_System_Action_1_RestSharp_HttpResponse + 52
7 gowonline 0x001142b4 m_RestSharp_Http_RequestStreamCallback_System_IAsyncResult_System_Action_1_RestSharp_HttpResponse + 900
8 gowonline 0x00329c60 m_2be7 + 48
9 gowonline 0x00a39d08 m_System_Net_WebAsyncResult_DoCallback + 76
10 gowonline 0x00a29628 m_System_Net_HttpWebRequest_SetWriteStream_System_Net_WebConnectionStream + 536
11 gowonline 0x00a46f84 m_System_Net_WebConnection_InitConnection_object + 708
12 gowonline 0x0101ffac m_wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 200
13 gowonline 0x017792d4 mono_jit_runtime_invoke + 2152
14 gowonline 0x0181b324 mono_runtime_invoke + 132
15 gowonline 0x01820118 mono_runtime_invoke_array + 1448
16 gowonline 0x01820510 mono_message_invoke + 444
17 gowonline 0x018444a8 mono_async_invoke + 124
18 gowonline 0x01844174 async_invoke_thread + 312
19 gowonline 0x0184c580 start_wrapper + 496
20 gowonline 0x018695b4 thread_start_routine + 284
21 gowonline 0x01885750 GC_start_routine + 92
22 libsystem_pthread.dylib 0x38ecdc5a _pthread_body + 138
23 libsystem_pthread.dylib 0x38ecdbca _pthread_start + 98
好的,那么已经肯定是在咱们使用的一个第三方类库RestSharp中出现的问题,问题是出如今一个Action回调的地方。那么这种问题为何会出现呢,那咱们就得好好得来找找缘由了。
关于如何查看iOS崩溃日志,让崩溃日志更加友好,咱们能够参考这篇文章,iOS应用崩溃日志揭秘,主要就是要确保你的设备上跑着的这个App的编译和打包的二进制文件要在你用于查看日志的Mac上,这样的话,当咱们查看崩溃日志的时候,Xcode会自动将那些没法阅读的函数调用的堆栈信息转化成可读性较强的日志信息,帮助仍是很大的。
那么这个时候咱们能够经过将设备链接到Mac上,直接经过Xcode将程序编译并运行,多尝试着玩一段时间,当程序再次出现崩溃的时候,咱们就能看到更清楚的函数调用关系了,同时也能看到更多的日志提示。
最终能肯定每次崩溃的函数就是这个mono_convert_imt_slot_to_vtable_slot,这个看上去就是Mono Runtime在将接口声明的方法指针指向实际实现这个接口的对方的方法,咱们能够找到mono_convert_imt_slot_to_vtable_slot这个方法所在的文件查看一下,这个方法就在Mono项目的目录mono/mini/mini-trampolines.c中能够找到。
在Xcode中崩溃时,会输出相似” SIGABRT (ERROR:mini-trampolines.c:183:mono_convert_imt_slot_to_vtable_slot: code should not be reached) “的日志,看着很像是本来是要执行某个方法,可是不知道由于什么缘由这个方法就没法访问到了,好奇葩啊。
解决方案
如今虽然已经知道了问题出现的地方,可是貌似彻底看不明白的样子,尼玛trampoline都仍是第一次据说耶,那么先请教一个我大Google吧,咱们老是相信本身不是那第一个吃螃蟹的人,因此咱们找到了一位大神的解决方案就在这里,大神的文章写得很是言简意赅,大致意思就是若是你在作Unity3D开发时,特别是在针对iOS和Android平台的时候,你颇有可能会碰到比较杯具的就是程序会莫名其妙地闪退哦,不过不要着急,这个一般就是由于你的程序编译的时候给trampoline分配的空间过小,而你的程序中又大量使用了泛型、泛型方法调用和接口实现致使的。而后给出了具体的解决方法,那就是在Unity3D的编译选项Player Setting中有一个AOT Compilation Options条目,在这个选项条目中加上如下编译参数就行了
nrgctx-trampolines=8096,nimt-trampolines=8096,ntrampolines=4048
而后再从新一下,多多测试吧,骚年。关于这三个参数的意思呢,大神也给出了解释,分别以下:
Mono Runtime AOT机制剖析
虽然问题貌似已经获得解决了,并且咱们貌似也搞清楚了具体缘由就是由于默认Mono Runtime在AOT编译的时候给的trampoline配置过小,不适合咱们这种设计优良,大量使用interface,设计绝对遵守OO思想的稍大一些的项目呢。那么咱们之后是否是在作Unity3D开发的时候就尽可能少用接口呢?是否是咱们就尽可能少用泛型和泛型方法呢?
既然这么感兴趣,想问个究竟,那么咱们就来好好看看这个AOT究竟是个神马东西吧,尼玛为何就这么复杂,这么隐蔽,这么折腾人,《铁血战神》在App Store上线都5个月了有木有,尼玛这个问题碰到也不是一次两次了有木有,做为程序猿的咱们被玩家吐槽了不少次,咱们的客服XDJM们为咱们背了多少黑锅啊,我勒个去啊。
首先,仍是先搞定这个trampoline吧,毕竟问题的根源是在它身上的,那么咱们就好好来看看这是个神马东西。咱们找到Mono Runtime的官方文档中关于trampoline的描述来看看吧。
Trampolines are small, hand-written pieces of assembly code used to perform various tasks in the mono runtime. They are generated at runtime using the native code generation macros used by the JIT. They usually have a corresponding C function they can fall back to if they need to perform a more complicated task. They can be viewed as ways to pass control from JITted code back to the runtime.
翻译一下吧:
Trampoline是一些手写的很是短小的用来在mono运行时中执行不少操做的组件代码。主要是经过JIT使用到的本地代码宏在运行时动态生成的。它们一般都有与之相对应的C方法,在某些较为复杂的场景中,当trampoline没法胜任时,mono运行时就会将这些复杂的操做交回给这些对应的C方法来执行。这也能够看做是将JIT代码的执行权交回给runtime的一种方式。
好吧,貌似尚未太明白,那么这个Trampoline为何会致使出现闪退的问题的,这看起来明显是为了提升mono runtime在执行C#代码时候的效率啊。
那么咱们再来看看官方文档关于JIT Trampolines和AOT Trampolines的介绍吧,杯具的IMT Trampolines介绍还在//TODO状态中。
JIT Trampolines These trampolines are used to JIT compile a method the first time it is called. When the JIT compiles a call instruction, it doesn’t compile the called method right away. Instead, it creates a JIT trampoline, and emits a call instruction referencing the trampoline. When the trampoline is called, it calls mono_magic_trampoline () which compiles the target method, and returns the address of the compiled code to the trampoline which branches to it. This process is somewhat slow, so mono_magic_trampoline () tries to patch the calling JITted code so it calls the compiled code instead of the trampoline from now on. This is done by mono_arch_patch_callsite () in tramp-.c.
好吧,再翻译一下吧。
JIT Trampolines 这些Trampoline主要是JIT在首次调用某个方法的时候编译方法用的。当JIT在编译一个方法调用指令时,它并不会马上就编译这个被调用到的方法。实际上,它会先建立一个JIT Trampoline,同时建立一个指向这个trampoline的调用指令。当这个JIT Trampoline在调用到的时候,它会再调用mono_magic_trampoline()方法来编译这个trampoline实际指向的目标方法,而后将编译后的方法的指针地址返回给这个指向它的trampoline。这个过程呢稍微有点慢,因此呢,mono_magic_trampoline()方法会优化调用JIT代码的过程,它会先尝试调用已经经过JIT编译过的方法而不是当即经过trampoline直接进行调用。这些都是经过在tramp-.c文件中的mono_patch_callsiete()方法来完成的。
这就是JIT Trampolines的机制,接下来咱们看看AOT Trampolines又是怎么一回事呢。
AOT Trampolines
These are similar to the JIT trampolines but instead of receiving a MonoMethod to compile, they receive an image+token pair. If the method identified by this pair is also AOT compiled, the address of its compiled code can be obtained without loading the metadata for the method.
再翻译一下。
AOT Trampolines AOT Trampolines和JIT Trampolines很是类似,可是AOT Trampolines接受的编译参数不是一个Mono方法而是一个image+token对。若是传入的用于编译的image+token对所指向的方法已经通过AOT编译过了,那么再次编译这个image+token对时,就会直接返回这个已编译方法的指针地址而不须要再次加载这个方法的元数据进行再次编译了。
好吧,看了这么多关于Trampoline相关的内容,貌似只是了解到了很是有限的内容,那就依然是Trampolines存在的价值就是为了减小C#代码在mono runtime中运行时的性能损耗,提升C#代码的执行效率。
还有那个没有出场的IMT Trampolines应该也就是用于优化接口调用效率的小『蹦床』吧。
那么咱们在开发Unity3D游戏的时候一般都会发布到iOS设备和Android设备上,而Unity3D在iOS和Android设备上的发布都选择了使用AOT编译机制来实现。那么显然咱们碰到的Trampolines问题都是跟AOT Trampolines有关,那么AOT又是神马呢?
AOT就是区别于JIT(Just In Time)的另外一个编译机制,全称是Ahead Of Time,就是预先编译好,而不是在代码执行到了某个方法再进行编译,这样的话会有一些好处。
经过查看Mono官方AOT介绍文档,使用AOT编译的有点有如下优势: 1. 加快程序启动速度 2. 更强的内存共享机制 3. 潜在的性能提高
固然也会有一些限制,例如支持平台的有限,支持AOT的Mono版本有限等等,具体信息能够参考Mono官方AOT介绍文档。
那么回到咱们最开始的问题,为何咱们的游戏就会出现崩溃呢?好吧,如今一点点回顾吧。
咱们出现的问题是偶尔会出现闪退,根据崩溃日志咱们能定位到是mono_convert_imt_slot_to_vtable_slot这个方法致使的,而后咱们再经过Xcode跟踪到了是trampoline没法被访问到的问题。
那么这么高端大气上档次的问题是肿么出现的呢?貌似Mono还算是个不错的产品啊,仍是很活跃的啊,也有专门的公司Xamarin在支撑着,怎么就会出现这种问提呢?
好吧,程序都是人写的,有问题也是很正常的。上面的分析已经很清楚了,大致的缘由就是由于Mono在iOS/Android等移动设备上使用了AOT这种机制,为何选择这种机制?缘由很是简单,那就是能够针对特定平台编译成在平台优化的字节码,在资源比较紧缺的移动平台上仍是有着明显优点的。而使用AOT编译就须要为Trampolines这些小东西留足足够的空间,固然这个确定是硬编码的某个常数啦,在整个程序加载成功运行以后,该常数就成为了Trampolines运行时的配置。AOT默认编译时给Trampolines的参数有点低:
nrgctx-trampolines 默认为1024
nimt-trampolines 默认为128
ntrampolines 默认为1024
这对于小一些的项目多是够用的,由于总体项目的结构不会太复杂,使用到的接口、泛型、递归相对也不会太多,可是对于一个稍大一些的项目来讲,特别是采用了某些设计良好的第三方库的项目来讲,这就比较纠结了。
其实咱们在项目中就使用了两个第三方的库,一个是CodeTitan.JSon库,一个是RestSharp,分别用于JSON解析和HTTP请求处理,但是这两个库实在是设计得太好了,各类使用接口,各类抽象,没个两三天我都无法说彻底理解了整个库的结构。
就是由于这些设计良好,彻底遵循OOP原则,高度抽象的类库将Mono默认的Trampolines的配置耗尽了,因此捏,咱们就把这个编译选项开大就行了,解决方案就是上面我们提到的咯。