iOS 16 UIResponderForwarderWantsForwardingFromResponder Crash问题解决方案

背景

最近后台统计发现有一个随机的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;
}