Permalink
Browse files

[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<RCTViewNodeProtocol> view) {
  count ++;
  if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
    [view reactBridgeDidFinishTransaction];
  }
});
NSLog(@"Views iterated: %zd", count);
```
The output after scrolling 20 sections of the `<ListView> - Paging` example was

```
2015-06-01 00:47:07.351 UIExplorer[67675:1709506] Views iterated: 1549
```

*every frame*

After the change

```
for (id<RCTViewNodeProtocol> 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.
  • Loading branch information...
tadeuzagallo committed Jun 1, 2015
1 parent df58789 commit b03446e27e99e2d6117190c08ffff1bb5f3495a3
Showing with 17 additions and 13 deletions.
  1. +17 −13 React/Modules/RCTUIManager.m
@@ -197,7 +197,8 @@ @implementation RCTUIManager
NSMutableDictionary *_defaultViews; // Main thread only
NSDictionary *_viewManagers;
NSDictionary *_viewConfigs;
NSUInteger _rootTag;
NSMutableSet *_bridgeTransactionListeners;
}
@synthesize bridge = _bridge;
@@ -263,7 +264,8 @@ - (instancetype)init
// Internal resources
_pendingUIBlocks = [[NSMutableArray alloc] init];
_rootViewTags = [[NSMutableSet alloc] init];
_rootTag = 1;
_bridgeTransactionListeners = [[NSMutableSet alloc] init];
}
return self;
}
@@ -287,6 +289,7 @@ - (void)invalidate
_rootViewTags = nil;
_shadowViewRegistry = nil;
_viewRegistry = nil;
_bridgeTransactionListeners = nil;
_bridge = nil;
[_pendingUIBlocksLock lock];
@@ -397,6 +400,10 @@ - (void)_purgeChildren:(NSArray *)children fromRegistry:(RCTSparseArray *)regist
[(id<RCTInvalidating>)subview invalidate];
}
registry[subview.reactTag] = nil;
if (registry == _viewRegistry) {
[_bridgeTransactionListeners removeObject:subview];
}
});
}
}
@@ -482,7 +489,6 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
}
// 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 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
}
/**
* 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<RCTViewNodeProtocol> view) {
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
[view reactBridgeDidFinishTransaction];
}
});
for (id<RCTViewNodeProtocol> node in _bridgeTransactionListeners) {
[node reactBridgeDidFinishTransaction];
}
};
}
@@ -844,6 +844,10 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
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;
}];

3 comments on commit b03446e

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Jun 1, 2015

I like it 👍

I like it 👍

@tadeuzagallo

This comment has been minimized.

Show comment
Hide comment
@tadeuzagallo

tadeuzagallo Jun 1, 2015

Owner

On ~10k views it went from ~20ms to ~0.05ms 😃

Owner

tadeuzagallo replied Jun 1, 2015

On ~10k views it went from ~20ms to ~0.05ms 😃

@brentvatne

This comment has been minimized.

Show comment
Hide comment

❤️

Please sign in to comment.