Skip to content

Commit 31371bf

Browse files
committed
fix(ios): prevent RCTGenericDelegateSplitter crash when UIKit invokes selector on empty delegates
Summary ======= RCTGenericDelegateSplitter stores delegates in an NSHashTable weakObjectsHashTable. When the splitter is installed as UIScrollView.delegate, UIKit caches which optional selectors the delegate responds to. If iOS later deallocates the underlying delegate objects under memory pressure while the app is suspended, the weak hash table silently drops the references. The splitter itself is still retained by UIScrollView. When the app returns to foreground, UIAnimator resumes an in-flight UIScrollViewScrollAnimation left over from setContentOffset:animated:YES. UIKit calls into the cached delegate (the splitter) and the Objective-C runtime asks for a method signature via methodSignatureForSelector:, which returns nil because _delegates is now empty. The runtime then invokes doesNotRecognizeSelector: and raises NSInvalidArgumentException. Fix === Fall back to a valid NSMethodSignature when no delegate handles the selector, so forwardInvocation: can safely no-op instead of raising. The signature is resolved from UIScrollViewDelegate's protocol method descriptions (all methods on that protocol are optional), with a permissive v@:@ signature as a last resort. Impact ====== Observed in a production RN 0.82.1 app: 273 fatal occurrences / 128 affected users over ~3 months, across iOS 17.6 - 26.3.1 and iPhone 13 - 17 Pro. Strongly correlated with low free_memory (<400MB) and long background suspension. Related ======= Commit e7ef992 addressed a closely related dealloc-ordering crash by setting _scrollView.delegate = nil on dealloc. That fix does not cover the foreground-resume path described here, because the splitter survives but its weak delegates do not.
1 parent 3982315 commit 31371bf

1 file changed

Lines changed: 13 additions & 1 deletion

File tree

packages/react-native/React/Fabric/Utils/RCTGenericDelegateSplitter.mm

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#import "RCTGenericDelegateSplitter.h"
99

10+
#import <objc/runtime.h>
11+
1012
@implementation RCTGenericDelegateSplitter {
1113
NSHashTable *_delegates;
1214
}
@@ -73,7 +75,17 @@ - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
7375
return [delegate methodSignatureForSelector:selector];
7476
}
7577
}
76-
return nil;
78+
// Fallback: prevent `unrecognized selector` crash when UIKit invokes a cached
79+
// delegate selector after all weak delegates have been released (e.g. background
80+
// suspension with in-flight UIScrollViewScrollAnimation). Resolve the signature
81+
// from UIScrollViewDelegate's optional methods first; otherwise use a permissive
82+
// `v@:@` signature so forwardInvocation: can safely no-op.
83+
struct objc_method_description desc =
84+
protocol_getMethodDescription(@protocol(UIScrollViewDelegate), selector, NO, YES);
85+
if (desc.types != NULL) {
86+
return [NSMethodSignature signatureWithObjCTypes:desc.types];
87+
}
88+
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
7789
}
7890

7991
- (void)forwardInvocation:(NSInvocation *)invocation

0 commit comments

Comments
 (0)