iOS闪退堆栈[UIKeyboardTaskEntry dealloc]问题修复

1,805 阅读2分钟

闪退复现:

当键盘弹出时点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

总结:

  1. 尽可能的不要对系统的类做 Hook 操作,可能产生意外的后果
  2. 如果要做动态安全防护, iOS11 以下版本的系统中需要将这个部分代码放入 MRC 环境中运行