Skip to content

Commit f46080a

Browse files
authored
[iOS] Fix concurrent modification crash (#4008)
## Description In `reattachHandlersIfNeeded` method we are iterating over handlers contained in registry. The following crash was reported: ``` NSDictionary was mutated while being enumerated ``` This PR adds `@synchronized` on `registry._handlers` so attempting to modify this value should be safe. > [!NOTE] > For now we have no consistent reproduction. I'm also not sure where this concurrent modification takes place. It seems to be indirect, as previous keyframe in provided stack trace was `flushOperations`. ## Test plan Tested on the example code from #3964
1 parent 58c4641 commit f46080a

2 files changed

Lines changed: 48 additions & 19 deletions

File tree

packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,19 @@ - (void)reattachHandlersIfNeeded
250250
// display:none and siblings change, the native UIView backing a component may be recycled
251251
// and replaced. maybeBindHandler is a no-op if the view is nil or unchanged. This is only
252252
// needed for handlers using the old api.
253-
for (RNGestureHandler *handler in _registry.handlers.objectEnumerator) {
254-
if (handler.viewTag == nil || [handler usesNativeOrVirtualDetector]) {
255-
continue;
256-
}
253+
NSDictionary *handlers = _registry.handlers;
254+
255+
@synchronized(handlers) {
256+
for (RNGestureHandler *handler in handlers.objectEnumerator) {
257+
if (handler.viewTag == nil || [handler usesNativeOrVirtualDetector]) {
258+
continue;
259+
}
257260

258-
[self maybeBindHandler:handler.tag
259-
toViewWithTag:handler.viewTag
260-
withActionType:handler.actionType
261-
withHostDetector:nil];
261+
[self maybeBindHandler:handler.tag
262+
toViewWithTag:handler.viewTag
263+
withActionType:handler.actionType
264+
withHostDetector:nil];
265+
}
262266
}
263267
}
264268

packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.m

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,29 @@ - (instancetype)init
2929

3030
- (RNGestureHandler *)handlerWithTag:(NSNumber *)handlerTag
3131
{
32-
return _handlers[handlerTag];
32+
@synchronized(_handlers) {
33+
return _handlers[handlerTag];
34+
}
3335
}
3436

3537
- (void)registerGestureHandler:(RNGestureHandler *)gestureHandler
3638
{
37-
_handlers[gestureHandler.tag] = gestureHandler;
39+
@synchronized(_handlers) {
40+
_handlers[gestureHandler.tag] = gestureHandler;
41+
}
3842
}
3943

4044
- (void)attachHandlerWithTag:(NSNumber *)handlerTag
4145
toView:(RNGHUIView *)view
4246
withActionType:(RNGestureHandlerActionType)actionType
4347
withHostDetector:(nullable RNGHUIView *)hostDetector
4448
{
45-
RNGestureHandler *handler = _handlers[handlerTag];
49+
RNGestureHandler *handler;
50+
51+
@synchronized(_handlers) {
52+
handler = _handlers[handlerTag];
53+
}
54+
4655
RCTAssert(handler != nil, @"Handler for tag %@ does not exists", handlerTag);
4756
[handler unbindFromView];
4857
handler.actionType = actionType;
@@ -55,27 +64,43 @@ - (void)attachHandlerWithTag:(NSNumber *)handlerTag
5564

5665
- (void)detachHandlerWithTag:(NSNumber *)handlerTag fromHostDetector:(RNGHUIView *)hostDetectorView
5766
{
58-
RNGestureHandler *handler = _handlers[handlerTag];
59-
if (handler.hostDetectorView != hostDetectorView)
67+
RNGestureHandler *handler;
68+
69+
@synchronized(_handlers) {
70+
handler = _handlers[handlerTag];
71+
}
72+
73+
if (handler.hostDetectorView != hostDetectorView) {
6074
return;
75+
}
76+
6177
[handler unbindFromView];
6278
}
6379

6480
- (void)dropHandlerWithTag:(NSNumber *)handlerTag
6581
{
66-
RNGestureHandler *handler = _handlers[handlerTag];
82+
RNGestureHandler *handler;
83+
84+
@synchronized(_handlers) {
85+
handler = _handlers[handlerTag];
86+
[_handlers removeObjectForKey:handlerTag];
87+
}
88+
6789
[handler unbindFromView];
68-
[_handlers removeObjectForKey:handlerTag];
6990
}
7091

7192
- (void)dropAllHandlers
7293
{
73-
for (NSNumber *tag in _handlers) {
74-
RNGestureHandler *handler = _handlers[tag];
75-
[handler unbindFromView];
94+
NSArray<RNGestureHandler *> *handlers;
95+
96+
@synchronized(_handlers) {
97+
handlers = [_handlers allValues];
98+
[_handlers removeAllObjects];
7699
}
77100

78-
[_handlers removeAllObjects];
101+
for (RNGestureHandler *handler in handlers) {
102+
[handler unbindFromView];
103+
}
79104
}
80105

81106
@end

0 commit comments

Comments
 (0)