[React Native] Remove layout-only nodes

Summary:
Remove layout-only views. Works by checking properties against a list of known properties that only affect layout. The `RCTShadowView` hierarchy still has a 1:1 correlation with the JS nodes.

This works by adjusting the tags and indices in `manageChildren`. For example, if JS told us to insert tag 1 at index 0 and tag 1 is layout-only with children whose tags are 2 and 3, we adjust it so we insert tags 2 and 3 at indices 0 and 1. This keeps changes out of `RCTView` and `RCTScrollView`. In order to simplify this logic, view moves are now processed as view removals followed by additions. A move from index 0 to 1 is recorded as a removal of view at indices 0 and 1 and an insertion of tags 1 and 2 at indices 0 and 1. Of course, the remaining indices have to be offset to take account for this.

The `collapsible` attribute is a bit of a hack to force `RCTScrollView` to always have one child. This was easier than rethinking out the logic there, but we could change this later.

@public

Test Plan: There are tests in `RCTUIManagerTests.m` that test the tag- and index-manipulation logic works. There are various scenarios including add-only, remove-only, and move. In addition, two scenario tests verify that the optimization works by checking the number of views and shadow views after various situations happen.
This commit is contained in:
Alex Akers
2015-06-25 09:07:32 -07:00
parent 64d7933e66
commit 3c5b4b0a9f
13 changed files with 2022 additions and 113 deletions

View File

@@ -367,8 +367,10 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (NSString *)description
{
NSString *description = super.description;
description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)];
return description;
if (self.layoutOnly) {
description = [@"* " stringByAppendingString:description];
}
return [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)];
}
- (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level
@@ -392,6 +394,82 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
return description;
}
- (BOOL)isLayoutOnly
{
if (![self.viewName isEqualToString:@"RCTView"]) {
// For now, only `RCTView`s can be layout-only.
return NO;
}
// dispatch_once is unnecessary because this property SHOULD only be accessed
// on the shadow queue
static NSSet *layoutKeys;
if (!layoutKeys) {
// Taken from LayoutPropTypes.js with the exception that borderWidth,
// borderTopWidth, borderBottomWidth, borderLeftWidth, and borderRightWidth
// were removed because black color is assumed
static NSString *const keys[] = {
@"width",
@"height",
@"top",
@"left",
@"right",
@"bottom",
@"margin",
@"marginVertical",
@"marginHorizontal",
@"marginTop",
@"marginBottom",
@"marginLeft",
@"marginRight",
@"padding",
@"paddingVertical",
@"paddingHorizontal",
@"paddingTop",
@"paddingBottom",
@"paddingLeft",
@"paddingRight",
@"position",
@"flexDirection",
@"flexWrap",
@"justifyContent",
@"alignItems",
@"alignSelf",
@"flex",
// Special case is handled below.
@"collapsible",
};
layoutKeys = [NSSet setWithObjects:keys count:sizeof(keys)/sizeof(*keys)];
}
NSNumber *collapsible = self.allProps[@"collapsible"];
if (collapsible && !collapsible.boolValue) {
return NO;
}
for (NSString *key in self.allProps) {
if (![layoutKeys containsObject:key]) {
return NO;
}
}
return YES;
}
- (CGRect)adjustedFrame
{
CGRect frame = self.frame;
RCTShadowView *superview = self;
while ((superview = superview.superview) && superview.layoutOnly) {
const CGRect superviewFrame = superview.frame;
frame.origin.x += superviewFrame.origin.x;
frame.origin.y += superviewFrame.origin.y;
}
return frame;
}
// Margin
#define RCT_MARGIN_PROPERTY(prop, metaProp) \