闪退复现:
当键盘弹出时点home键进入后台,再切换回前台时会出现crash。iOS8上能够稳定复现。查看闪退日志从iOS8-iOS10都有闪退现象
1.iOS8闪退堆栈信息
libobjc.A.dylib objc_msgSend + 28
1 libsystem_blocks.dylib _Block_release + 256
2 libobjc.A.dylib (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 564
3 CoreFoundation _CFAutoreleasePoolPop + 28
4 UIKit __wrapRunLoopWithAutoreleasePoolHandler + 76
5 CoreFoundation ___CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
6 CoreFoundation ___CFRunLoopDoObservers + 360
7 CoreFoundation ___CFRunLoopRun + 836
8 CoreFoundation CFRunLoopRunSpecific + 396
9 GraphicsServices GSEventRunModal + 168
10 UIKit UIApplicationMain + 1488
2.iOS9闪退堆栈信
libobjc.A.dylib objc_release + 16
1 libsystem_blocks.dylib _Block_release + 156
2 libobjc.A.dylib (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 508
3 CoreFoundation _CFAutoreleasePoolPop + 28
4 UIKit __prepareForCAFlush + 352
5 UIKit __afterCACommitHandler + 160
6 CoreFoundation ___CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
7 CoreFoundation ___CFRunLoopDoObservers + 372
8 CoreFoundation ___CFRunLoopRun + 928
9 CoreFoundation CFRunLoopRunSpecific + 384
10 GraphicsServices GSEventRunModal + 180
11 UIKit UIApplicationMain + 204
3.iOS10闪退堆栈信息
libobjc.A.dylib objc_object::release() + 8
1 libsystem_blocks.dylib _Block_release + 160
2 UIKit -[UIKeyboardTaskEntry dealloc] + 68
3 libobjc.A.dylib (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 836
4 CoreFoundation _CFAutoreleasePoolPop + 28
5 UIKit __prepareForCAFlush + 596
6 UIKit __afterCACommitHandler + 236
7 CoreFoundation ___CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
8 CoreFoundation ___CFRunLoopDoObservers + 372
9 CoreFoundation ___CFRunLoopRun + 956
10 CoreFoundation CFRunLoopRunSpecific + 424
11 GraphicsServices GSEventRunModal + 100
12 UIKit UIApplicationMain + 208问题分析:
Swizzle 来动态拦截NSArray、NSMutableArray、NSDictionary、NSMutableDictionary的方法,这种通过 Hook 系统类的方式来达到动态防护下标越界等行为的操作,有很大的隐患。项目里添加过几个 Category 用以防止数组越界、字典插入 nil 等操作。通过Build Phases中 -> Compile Sources 找到了这几个 Category,涉及比较多 runtime 的代码,最好不要用 ARC,结合 Crash 的 Log 情况来看,决定把这几个 Category 替换成 MRC 。
iOS 的内存核心之一就哈希表,而 Objective-C 中的 NSDictionary 底层其实是就是一个哈希表,根本原因就应该是对 NSDictionary 以及 NSMutableDictionary 进行 Swizzle 的 Category 了。在将这两个 Category 的 Compiler Flags 加入 -fno-objc-arc 后,重新复测 Crash 步骤,Zombie 没有再捕获到任何野指针的出现,这个 Crash 被彻底修复了。
解决方案:
通过Build Phases中 -> Compile Sources 找到了这几个 Category改成mac:-fno-objc-arc
总结:
- 尽可能的不要对系统的类做 Hook 操作,可能产生意外的后果
- 如果要做动态安全防护, iOS11 以下版本的系统中需要将这个部分代码放入 MRC 环境中运行