Reduce boxing overhead of arrays in uiBlockWithLayoutUpdateForRootView

Summary: The view update cycle in UIManager was relying on a bunch of boolean values boxes as NSNumbers in parallel arrays. This diff packs those values into a struct, which is more efficient and easier to maintain.

Reviewed By: javache

Differential Revision: D3365346

fbshipit-source-id: d9cbf2865421f76772c1761b13992d40ec3675f0
This commit is contained in:
Nick Lockwood 2016-05-31 08:38:47 -07:00 committed by Facebook Github Bot 3
parent 0b6764d18e
commit 5136d95f2c
2 changed files with 73 additions and 74 deletions

View File

@ -1014,12 +1014,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.react.uiapp;
PRODUCT_NAME = UIExplorer;
TARGETED_DEVICE_FAMILY = "1,2";
WARNING_CFLAGS = (
"-Wextra",
"-Wall",
"-Wincompatible-pointer-types",
"-Wincompatible-pointer-types-discards-qualifiers",
);
};
name = Debug;
};
@ -1039,12 +1033,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.react.uiapp;
PRODUCT_NAME = UIExplorer;
TARGETED_DEVICE_FAMILY = "1,2";
WARNING_CFLAGS = (
"-Wextra",
"-Wall",
"-Wincompatible-pointer-types",
"-Wincompatible-pointer-types-discards-qualifiers",
);
};
name = Release;
};
@ -1149,6 +1137,8 @@
"-Wextra",
"-Wall",
"-Wincompatible-pointer-types",
"-Wincompatible-pointer-types-discards-qualifiers",
"-Wshadow",
);
};
name = Debug;
@ -1212,6 +1202,8 @@
"-Wextra",
"-Wall",
"-Wincompatible-pointer-types",
"-Wincompatible-pointer-types-discards-qualifiers",
"-Wshadow",
);
};
name = Release;

View File

@ -57,8 +57,6 @@ NSString *const RCTUIManagerRootViewKey = @"RCTUIManagerRootViewKey";
@property (nonatomic, readonly) NSTimeInterval duration;
@property (nonatomic, readonly) NSTimeInterval delay;
@property (nonatomic, readonly, copy) NSString *property;
@property (nonatomic, readonly) id fromValue;
@property (nonatomic, readonly) id toValue;
@property (nonatomic, readonly) CGFloat springDamping;
@property (nonatomic, readonly) CGFloat initialVelocity;
@property (nonatomic, readonly) RCTAnimationType animationType;
@ -136,8 +134,6 @@ static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnim
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
_initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
}
_fromValue = config[@"fromValue"];
_toValue = config[@"toValue"];
}
return self;
}
@ -552,42 +548,47 @@ dispatch_queue_t RCTGetUIManagerQueue(void)
return nil;
}
// Parallel arrays are built and then handed off to main thread
NSMutableArray<NSNumber *> *frameReactTags =
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableArray<NSValue *> *frames =
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableArray<NSNumber *> *areNew =
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableArray<NSNumber *> *parentsAreNew =
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableDictionary<NSNumber *, RCTViewManagerUIBlock> *updateBlocks =
[NSMutableDictionary new];
NSMutableArray<NSNumber *> *areHidden =
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
typedef struct {
CGRect frame;
BOOL isNew;
BOOL parentIsNew;
BOOL isHidden;
} RCTFrameData;
for (RCTShadowView *shadowView in viewsWithNewFrames) {
[frameReactTags addObject:shadowView.reactTag];
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
[areNew addObject:@(shadowView.isNewView)];
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
[areHidden addObject:@(shadowView.isHidden)];
}
for (RCTShadowView *shadowView in viewsWithNewFrames) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
// Construct arrays then hand off to main thread
NSInteger count = viewsWithNewFrames.count;
NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
{
NSInteger index = 0;
RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
for (RCTShadowView *shadowView in viewsWithNewFrames) {
reactTags[index] = shadowView.reactTag;
frameDataArray[index++] = (RCTFrameData){
shadowView.frame,
shadowView.isNewView,
shadowView.superview.isNewView,
shadowView.isHidden,
};
}
}
// These are blocks to be executed on each view, immediately after
// reactSetFrame: has been called. Note that if reactSetFrame: is not called,
// these won't be called either, so this is not a suitable place to update
// properties that aren't related to layout.
NSMutableDictionary<NSNumber *, RCTViewManagerUIBlock> *updateBlocks =
[NSMutableDictionary new];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
NSNumber *reactTag = shadowView.reactTag;
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
if (block) {
updateBlocks[shadowView.reactTag] = block;
updateBlocks[reactTag] = block;
}
if (shadowView.onLayout) {
@ -602,8 +603,7 @@ dispatch_queue_t RCTGetUIManagerQueue(void)
});
}
if (RCTIsReactRootView(shadowView.reactTag)) {
NSNumber *reactTag = shadowView.reactTag;
if (RCTIsReactRootView(reactTag)) {
CGSize contentSize = shadowView.frame.size;
dispatch_async(dispatch_get_main_queue(), ^{
@ -618,23 +618,28 @@ dispatch_queue_t RCTGetUIManagerQueue(void)
// Perform layout (possibly animated)
return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
RCTLayoutAnimation *layoutAnimation = _layoutAnimation;
const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes;
RCTLayoutAnimation *layoutAnimation = uiManager->_layoutAnimation;
__block NSUInteger completionsCalled = 0;
for (NSUInteger ii = 0; ii < frames.count; ii++) {
NSNumber *reactTag = frameReactTags[ii];
UIView *view = viewRegistry[reactTag];
CGRect frame = [frames[ii] CGRectValue];
BOOL isHidden = [areHidden[ii] boolValue];
BOOL isNew = [areNew[ii] boolValue];
NSInteger index = 0;
for (NSNumber *reactTag in reactTags) {
RCTFrameData frameData = frameDataArray[index++];
UIView *view = viewRegistry[reactTag];
CGRect frame = frameData.frame;
BOOL isHidden = frameData.isHidden;
BOOL isNew = frameData.isNew;
RCTAnimation *updateAnimation = isNew ? nil : layoutAnimation.updateAnimation;
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew;
RCTAnimation *createAnimation = shouldAnimateCreation ? layoutAnimation.createAnimation : nil;
void (^completion)(BOOL) = ^(BOOL finished) {
completionsCalled++;
if (layoutAnimation.callback && completionsCalled == frames.count) {
if (layoutAnimation.callback && completionsCalled == count) {
layoutAnimation.callback(@[@(finished)]);
// It's unsafe to call this callback more than once, so we nil it out here
@ -647,58 +652,59 @@ dispatch_queue_t RCTGetUIManagerQueue(void)
view.hidden = isHidden;
}
// Animate view creation
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
if (createAnimation) {
// Animate view creation
[view reactSetFrame:frame];
CATransform3D finalTransform = view.layer.transform;
CGFloat finalOpacity = view.layer.opacity;
if ([createAnimation.property isEqualToString:@"scaleXY"]) {
NSString *property = createAnimation.property;
if ([property isEqualToString:@"scaleXY"]) {
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
} else if ([createAnimation.property isEqualToString:@"opacity"]) {
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = 0.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@",
createAnimation.property);
}
[createAnimation performAnimations:^{
if ([createAnimation.property isEqual:@"scaleXY"]) {
if ([property isEqualToString:@"scaleXY"]) {
view.layer.transform = finalTransform;
} else if ([createAnimation.property isEqual:@"opacity"]) {
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = finalOpacity;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@",
createAnimation.property);
}
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
if (updateBlock) {
updateBlock(self, _viewRegistry);
updateBlock(self, viewRegistry);
}
} withCompletionBlock:completion];
// Animate view update
} else if (updateAnimation) {
// Animate view update
[updateAnimation performAnimations:^{
[view reactSetFrame:frame];
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
if (updateBlock) {
updateBlock(self, _viewRegistry);
updateBlock(self, viewRegistry);
}
} withCompletionBlock:completion];
// Update without animation
} else {
[view reactSetFrame:frame];
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
// Update without animation
[view reactSetFrame:frame];
if (updateBlock) {
updateBlock(self, _viewRegistry);
updateBlock(self, viewRegistry);
}
completion(YES);
}
}
_layoutAnimation = nil;
// Clean up
uiManager->_layoutAnimation = nil;
};
}
@ -802,10 +808,11 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(nonnull NSNumber *)containe
// the view events anyway.
view.userInteractionEnabled = NO;
NSString *property = deleteAnimation.property;
[deleteAnimation performAnimations:^{
if ([deleteAnimation.property isEqual:@"scaleXY"]) {
if ([property isEqualToString:@"scaleXY"]) {
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
} else if ([deleteAnimation.property isEqual:@"opacity"]) {
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = 0.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@",