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: D3253073

fbshipit-source-id: 3e1520c27b88bc1b44ddffcaae3218d7681b2cd2
This commit is contained in:
Nick Lockwood 2016-05-27 04:46:25 -07:00 committed by Facebook Github Bot 0
parent a0562c7ccf
commit fc14f85f73
1 changed files with 116 additions and 95 deletions

View File

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