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:
parent
a0562c7ccf
commit
fc14f85f73
|
@ -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 %@",
|
||||||
|
|
Loading…
Reference in New Issue