背景
最近后台统计发现有一个随机的Crash,引起了我们的关注
从操作系统来看,都是iOS 16+ 系统
崩溃堆栈如下:
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001daa1808c
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [44398]
Triggered by Thread: 0
Kernel Triage:
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
Thread 0 name:
Thread 0 Crashed:
0 libsystem_platform.dylib 0x00000001daa1808c _os_unfair_lock_recursive_abort + 36 (lock.c:508)
1 libsystem_platform.dylib 0x00000001daa12898 _os_unfair_lock_lock_slow + 280 (lock.c:567)
2 libobjc.A.dylib 0x00000001859e28d4 objc_object::rootRetain_overflow(bool) + 112 (objc-object.h:684)
3 CoreFoundation 0x000000018c83d114 -[__NSDictionaryM __setObject:forKey:] + 352 (NSCollectionAux.h:40)
4 CoreFoundation 0x000000018c83b0e4 -[__NSFrozenDictionaryM __apply:context:] + 128 (NSDictionaryM_Common.h:247)
5 CoreFoundation 0x000000018c86bd60 -[CFPrefsSource mergeIntoDictionary:sourceDictionary:cloudKeyEvaluator:] + 168 (CFPrefsSource.m:965)
6 CoreFoundation 0x000000018c86bbc0 -[CFPrefsSearchListSource alreadylocked_getDictionary:] + 744 (CFPrefsSearchListSource.m:1243)
7 CoreFoundation 0x000000018c843394 -[CFPrefsSearchListSource alreadylocked_copyValueForKey:] + 172 (CFPrefsSearchListSource.m:639)
8 CoreFoundation 0x000000018c8432c8 -[CFPrefsSource copyValueForKey:] + 52 (CFPrefsSource.m:894)
9 CoreFoundation 0x000000018c84327c __76-[_CFXPreferences copyAppValueForKey:identifier:container:configurationURL:]_block_invoke + 32 (CFXPreferences.m:1085)
10 CoreFoundation 0x000000018c8c3ca4 __108-[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier:container:cloudConfigurationURL:perform:]_block_invoke + 392 (CFPrefsSearchListSource.m:1767)
11 CoreFoundation 0x000000018c8af878 normalizeQuintuplet + 356 (CFPrefsSearchListSource.m:74)
12 CoreFoundation 0x000000018c8ac124 -[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurationURL:perform:] + 152 (CFPrefsSearchListSource.m:1639)
13 CoreFoundation 0x000000018c852d5c -[_CFXPreferences copyAppValueForKey:identifier:container:configurationURL:] + 168 (CFXPreferences.m:1081)
14 CoreFoundation 0x000000018c852bb4 _CFPreferencesCopyAppValueWithContainerAndConfiguration + 112 (CFXPreferences.m:2160)
15 Foundation 0x0000000186ba586c -[NSUserDefaults(NSUserDefaults) objectForKey:] + 60 (NSUserDefaults.m:224)
16 CsdnPlus 0x0000000104c1f5ac 0x102ec0000 + 30799276
17 CsdnPlus 0x0000000104c842ac 0x102ec0000 + 31212204
18 CsdnPlus 0x0000000104c7c920 0x102ec0000 + 31181088
19 CsdnPlus 0x0000000104c783c4 0x102ec0000 + 31163332
20 CsdnPlus 0x0000000104c7bdcc 0x102ec0000 + 31178188
21 CsdnPlus 0x0000000104c79db0 0x102ec0000 + 31169968
22 libsystem_platform.dylib 0x00000001daa13a90 _sigtramp + 56 (sigtramp.c:116)
23 libsystem_kernel.dylib 0x00000001ca375bf0 abort_with_payload_wrapper_internal + 104 (terminate_with_reason.c:102)
24 libsystem_kernel.dylib 0x00000001ca375b88 abort_with_reason + 32 (terminate_with_reason.c:116)
25 libobjc.A.dylib 0x00000001859e3a5c _objc_fatalv(unsigned long long, unsigned long long, char const*, char*) + 116 (objc-errors.mm:199)
26 libobjc.A.dylib 0x00000001859e39e8 _objc_fatal(char const*, ...) + 32 (objc-errors.mm:215)
27 libobjc.A.dylib 0x00000001859b6c24 weak_register_no_lock + 392 (objc-weak.mm:421)
28 libobjc.A.dylib 0x00000001859bb88c objc_storeWeak + 484 (NSObject.mm:365)
29 UIKitCore 0x000000018eb1af2c _UIResponderForwarderWantsForwardingFromResponder + 736 (UITouch.m:185)
30 UIKitCore 0x000000018ea307dc __forwardTouchMethod_block_invoke + 44 (UIResponder.m:2168)
31 CoreFoundation 0x000000018c8313cc __NSSET_IS_CALLING_OUT_TO_A_BLOCK__ + 24 (NSSetHelpers.m:10)
32 CoreFoundation 0x000000018c8b2264 -[__NSSetM enumerateObjectsWithOptions:usingBlock:] + 200 (NSSetM_Common.h:157)
33 UIKitCore 0x000000018ebfddcc forwardTouchMethod + 236 (UIResponder.m:2167)
34 UIKitCore 0x000000018eaf81b0 -[UIWindow _sendTouchesForEvent:] + 356 (UIWindow.m:3160)
35 UIKitCore 0x000000018eaf7770 -[UIWindow sendEvent:] + 3284 (UIWindow.m:3481)
36 UIKitCore 0x000000018eaf6a20 -[UIApplication sendEvent:] + 676 (UIApplication.m:12635)
37 UIKitCore 0x000000018eaf60d8 __dispatchPreprocessedEventFromEventQueue + 7084 (UIEventDispatcher.m:2417)
38 UIKitCore 0x000000018eb3de00 __processEventQueue + 5632 (UIEventDispatcher.m:2726)
39 UIKitCore 0x000000018f79b820 updateCycleEntry + 168 (UIEventDispatcher.m:117)
40 UIKitCore 0x000000018f04e5b0 _UIUpdateSequenceRun + 84 (_UIUpdateSequence.mm:112)
41 UIKitCore 0x000000018f69d310 schedulerStepScheduledMainSection + 172 (_UIUpdateScheduler.m:987)
42 UIKitCore 0x000000018f69c4dc runloopSourceCallback + 92 (_UIUpdateScheduler.m:1079)
43 CoreFoundation 0x000000018c8fcf24 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 (CFRunLoop.c:1957)
44 CoreFoundation 0x000000018c9092fc __CFRunLoopDoSource0 + 176 (CFRunLoop.c:2001)
45 CoreFoundation 0x000000018c88d1c0 __CFRunLoopDoSources0 + 244 (CFRunLoop.c:2038)
46 CoreFoundation 0x000000018c8a2b7c __CFRunLoopRun + 836 (CFRunLoop.c:2953)
47 CoreFoundation 0x000000018c8a7eb0 CFRunLoopRunSpecific + 612 (CFRunLoop.c:3418)
48 GraphicsServices 0x00000001c6a9d368 GSEventRunModal + 164 (GSEvent.c:2196)
49 UIKitCore 0x000000018ed9d668 -[UIApplication _run] + 888 (UIApplication.m:3758)
50 UIKitCore 0x000000018ed9d2cc UIApplicationMain + 340 (UIApplication.m:5348)
51 CsdnPlus 0x0000000103650e04 0x102ec0000 + 7933444
52 dyld 0x00000001ab1a0960 start + 2528 (dyldMain.cpp:1170)
分析
堆栈
通过崩溃堆栈,我们可以分析出来是一个系统性的Crash,再往下我们定位到了objc-weak.mm:421
,查看源码后发现应该是一个过渡释放的问题导致的Crash。
行为轨迹
从崩溃堆栈我们可以看出来是系统的过渡释放问题导致的,那么需要找到用户的具体行为。才能继续分析。
通过用户行为轨迹分析发现,所有的Crash用户都是在点击搜索框的时候出现的Crash,猜测是和输入键盘有关系。
找案例
因为是系统性Crash,我们坚信肯定也有人遇到相同的问题。果然在苹果官方开发者论坛发现了复现方法。
通过该方法试验,确实能够100%复现该问题。
debug 复现后控制台输出内容如下:
解决方案
在论坛中,有大牛已经给出了解决方案。将 UIWindow+FixCrashOnInputKeyboard.h/m
拖入项目中即可。
通过试验,确实解决了该问题。
代码如下:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = objc_getClass("UIWindow");
SEL originalSelector = @selector(sendEvent:);
SEL swizzledSelector = @selector(sendEventEx:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)sendEventEx:(UIEvent *)event {
if ([UIWindow isSendEventToDealloctingObject:event]) {
return;
}
[self sendEventEx: event];
}
#pragma mark - check if sending Event to deallocating object
+ (BOOL) isSendEventToDealloctingObject:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
for (UITouch *touch in event.allTouches) {
NSString *windowName = NSStringFromClass([touch.window class]);
if ([windowName isEqualToString:@"UIRemoteKeyboardWindow"]) {
UIView* view = touch.view;
UIResponder *arg2 = view.nextResponder;
NSString* responderClassName = NSStringFromClass([arg2 class]);
if ([responderClassName isEqualToString:@"_UIRemoteInputViewController"]) {
bool isDeallocating = false;
// Use 'performSelector' when u are develop a App-Store App.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
SEL sel = NSSelectorFromString(@"_isDeallocating");
isDeallocating = [arg2 respondsToSelector:sel] && [arg2 performSelector:sel];
#pragma clang diagnostic pop
if (isDeallocating) {
NSLog(@"UIWindow - BingGo a deallocating object ...");
return true;
}
}
}
}
}
return false;
}