From b03446e27e99e2d6117190c08ffff1bb5f3495a3 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 1 Jun 2015 03:01:55 -0700 Subject: [PATCH] [ReactNative] Stop traversing the whole view hierarchy every frame Summary: @public `RCTUIManager` would traverse the whole view hierarchy every time there was any call from JS to Native to call `reactBridgeDidFinishTransaction` on the views that would respond to it. This is a deprecated method that is only implemented by 3 classes, so for now we keep track of these views as they're created and just iterate through them on updates. Test Plan: > NOTE: I tested this on UIExplorer, since the internally none of the classes are used I tried to keep it simple, so I added the following to the old code: ``` __block NSUInteger count = 0; UIView *rootView = _viewRegistry[rootViewTag]; RCTTraverseViewNodes(rootView, ^(id view) { count ++; if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { [view reactBridgeDidFinishTransaction]; } }); NSLog(@"Views iterated: %zd", count); ``` The output after scrolling 20 sections of the ` - Paging` example was ``` 2015-06-01 00:47:07.351 UIExplorer[67675:1709506] Views iterated: 1549 ``` *every frame* After the change ``` for (id node in _bridgeTransactionListeners) { [node reactBridgeDidFinishTransaction]; } NSLog(@"Views iterated: %zd", _bridgeTransactionListeners.count); ``` ``` 2015-06-01 00:51:23.715 UIExplorer[70355:1716465] Views iterated: 3 ``` No matter how many pages are loaded, the output is always 3. --- React/Modules/RCTUIManager.m | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 636df0406..127cbd9fc 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -197,7 +197,8 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio NSMutableDictionary *_defaultViews; // Main thread only NSDictionary *_viewManagers; NSDictionary *_viewConfigs; - NSUInteger _rootTag; + + NSMutableSet *_bridgeTransactionListeners; } @synthesize bridge = _bridge; @@ -263,7 +264,8 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa // Internal resources _pendingUIBlocks = [[NSMutableArray alloc] init]; _rootViewTags = [[NSMutableSet alloc] init]; - _rootTag = 1; + + _bridgeTransactionListeners = [[NSMutableSet alloc] init]; } return self; } @@ -287,6 +289,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa _rootViewTags = nil; _shadowViewRegistry = nil; _viewRegistry = nil; + _bridgeTransactionListeners = nil; _bridge = nil; [_pendingUIBlocksLock lock]; @@ -397,6 +400,10 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa [(id)subview invalidate]; } registry[subview.reactTag] = nil; + + if (registry == _viewRegistry) { + [_bridgeTransactionListeners removeObject:subview]; + } }); } } @@ -482,7 +489,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa } // Perform layout (possibly animated) - NSNumber *rootViewTag = rootShadowView.reactTag; return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTResponseSenderBlock callback = self->_layoutAnimation.callback; __block NSInteger completionsCalled = 0; @@ -547,17 +553,11 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa } /** - * Enumerate all active (attached to a parent) views and call - * reactBridgeDidFinishTransaction on them if they implement it. - * TODO: this is quite inefficient. If this was handled via the - * ViewManager instead, it could be done more efficiently. + * TODO(tadeu): Remove it once and for all */ - UIView *rootView = _viewRegistry[rootViewTag]; - RCTTraverseViewNodes(rootView, ^(id view) { - if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { - [view reactBridgeDidFinishTransaction]; - } - }); + for (id node in _bridgeTransactionListeners) { + [node reactBridgeDidFinishTransaction]; + } }; } @@ -844,6 +844,10 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag view.layer.allowsGroupOpacity = YES; // required for touch handling } RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager); + + if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { + [uiManager->_bridgeTransactionListeners addObject:view]; + } } viewRegistry[reactTag] = view; }];