Skip to content

Commit fc14f85

Browse files
nicklockwoodFacebook Github Bot 0
authored andcommitted
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
1 parent a0562c7 commit fc14f85

File tree

1 file changed

+119
-98
lines changed

1 file changed

+119
-98
lines changed

React/Modules/RCTUIManager.m

Lines changed: 119 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ @interface RCTAnimation : NSObject
5757
@property (nonatomic, readonly) NSTimeInterval duration;
5858
@property (nonatomic, readonly) NSTimeInterval delay;
5959
@property (nonatomic, readonly, copy) NSString *property;
60-
@property (nonatomic, readonly) id fromValue;
61-
@property (nonatomic, readonly) id toValue;
6260
@property (nonatomic, readonly) CGFloat springDamping;
6361
@property (nonatomic, readonly) CGFloat initialVelocity;
6462
@property (nonatomic, readonly) RCTAnimationType animationType;
@@ -136,8 +134,6 @@ - (instancetype)initWithDuration:(NSTimeInterval)duration dictionary:(NSDictiona
136134
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
137135
_initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
138136
}
139-
_fromValue = config[@"fromValue"];
140-
_toValue = config[@"toValue"];
141137
}
142138
return self;
143139
}
@@ -552,42 +548,47 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *
552548
return nil;
553549
}
554550

555-
// Parallel arrays are built and then handed off to main thread
556-
NSMutableArray<NSNumber *> *frameReactTags =
557-
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
558-
NSMutableArray<NSValue *> *frames =
559-
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
560-
NSMutableArray<NSNumber *> *areNew =
561-
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
562-
NSMutableArray<NSNumber *> *parentsAreNew =
563-
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
564-
NSMutableDictionary<NSNumber *, RCTViewManagerUIBlock> *updateBlocks =
565-
[NSMutableDictionary new];
566-
NSMutableArray<NSNumber *> *areHidden =
567-
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
568-
569-
for (RCTShadowView *shadowView in viewsWithNewFrames) {
570-
[frameReactTags addObject:shadowView.reactTag];
571-
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
572-
[areNew addObject:@(shadowView.isNewView)];
573-
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
574-
[areHidden addObject:@(shadowView.isHidden)];
575-
}
576-
577-
for (RCTShadowView *shadowView in viewsWithNewFrames) {
578-
// We have to do this after we build the parentsAreNew array.
579-
shadowView.newView = NO;
551+
typedef struct {
552+
CGRect frame;
553+
BOOL isNew;
554+
BOOL parentIsNew;
555+
BOOL isHidden;
556+
} RCTFrameData;
557+
558+
// Construct arrays then hand off to main thread
559+
NSInteger count = viewsWithNewFrames.count;
560+
NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
561+
NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
562+
{
563+
NSInteger index = 0;
564+
RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
565+
for (RCTShadowView *shadowView in viewsWithNewFrames) {
566+
reactTags[index] = shadowView.reactTag;
567+
frameDataArray[index++] = (RCTFrameData){
568+
shadowView.frame,
569+
shadowView.isNewView,
570+
shadowView.superview.isNewView,
571+
shadowView.isHidden,
572+
};
573+
}
580574
}
581575

582576
// These are blocks to be executed on each view, immediately after
583577
// reactSetFrame: has been called. Note that if reactSetFrame: is not called,
584578
// these won't be called either, so this is not a suitable place to update
585579
// properties that aren't related to layout.
580+
NSMutableDictionary<NSNumber *, RCTViewManagerUIBlock> *updateBlocks =
581+
[NSMutableDictionary new];
586582
for (RCTShadowView *shadowView in viewsWithNewFrames) {
583+
584+
// We have to do this after we build the parentsAreNew array.
585+
shadowView.newView = NO;
586+
587+
NSNumber *reactTag = shadowView.reactTag;
587588
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
588589
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
589590
if (block) {
590-
updateBlocks[shadowView.reactTag] = block;
591+
updateBlocks[reactTag] = block;
591592
}
592593

593594
if (shadowView.onLayout) {
@@ -602,8 +603,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *
602603
});
603604
}
604605

605-
if (RCTIsReactRootView(shadowView.reactTag)) {
606-
NSNumber *reactTag = shadowView.reactTag;
606+
if (RCTIsReactRootView(reactTag)) {
607607
CGSize contentSize = shadowView.frame.size;
608608

609609
dispatch_async(dispatch_get_main_queue(), ^{
@@ -618,87 +618,107 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *
618618

619619
// Perform layout (possibly animated)
620620
return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
621-
RCTLayoutAnimation *layoutAnimation = _layoutAnimation;
622-
623-
__block NSUInteger completionsCalled = 0;
624-
for (NSUInteger ii = 0; ii < frames.count; ii++) {
625-
NSNumber *reactTag = frameReactTags[ii];
626-
UIView *view = viewRegistry[reactTag];
627-
CGRect frame = [frames[ii] CGRectValue];
628-
629-
BOOL isHidden = [areHidden[ii] boolValue];
630-
BOOL isNew = [areNew[ii] boolValue];
631-
RCTAnimation *updateAnimation = isNew ? nil : layoutAnimation.updateAnimation;
632-
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
633-
RCTAnimation *createAnimation = shouldAnimateCreation ? layoutAnimation.createAnimation : nil;
634-
635-
void (^completion)(BOOL) = ^(BOOL finished) {
636-
completionsCalled++;
637-
if (layoutAnimation.callback && completionsCalled == frames.count) {
638-
layoutAnimation.callback(@[@(finished)]);
639-
640-
// It's unsafe to call this callback more than once, so we nil it out here
641-
// to make sure that doesn't happen.
642-
layoutAnimation.callback = nil;
643-
}
644-
};
645621

646-
if (view.isHidden != isHidden) {
647-
view.hidden = isHidden;
648-
}
622+
const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes;
649623

650-
// Animate view creation
651-
if (createAnimation) {
624+
RCTLayoutAnimation *layoutAnimation = uiManager->_layoutAnimation;
625+
if (!layoutAnimation.updateAnimation && !layoutAnimation.createAnimation) {
626+
627+
// Fast path for common case
628+
NSInteger index = 0;
629+
for (NSNumber *reactTag in reactTags) {
630+
RCTFrameData frameData = frameDataArray[index++];
631+
632+
UIView *view = viewRegistry[reactTag];
633+
CGRect frame = frameData.frame;
652634
[view reactSetFrame:frame];
653635

654-
CATransform3D finalTransform = view.layer.transform;
655-
CGFloat finalOpacity = view.layer.opacity;
656-
if ([createAnimation.property isEqualToString:@"scaleXY"]) {
657-
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
658-
} else if ([createAnimation.property isEqualToString:@"opacity"]) {
659-
view.layer.opacity = 0.0;
636+
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
637+
if (updateBlock) {
638+
updateBlock(self, viewRegistry);
660639
}
640+
}
641+
if (layoutAnimation.callback) {
642+
layoutAnimation.callback(@[@YES]);
643+
}
661644

662-
[createAnimation performAnimations:^{
663-
if ([createAnimation.property isEqual:@"scaleXY"]) {
664-
view.layer.transform = finalTransform;
665-
} else if ([createAnimation.property isEqual:@"opacity"]) {
666-
view.layer.opacity = finalOpacity;
667-
} else {
668-
RCTLogError(@"Unsupported layout animation createConfig property %@",
669-
createAnimation.property);
670-
}
645+
} else {
646+
647+
__block NSUInteger completionsCalled = 0;
648+
649+
NSInteger index = 0;
650+
for (NSNumber *reactTag in reactTags) {
651+
RCTFrameData frameData = frameDataArray[index++];
652+
653+
UIView *view = viewRegistry[reactTag];
654+
CGRect frame = frameData.frame;
655+
656+
BOOL isNew = frameData.isNew;
657+
RCTAnimation *updateAnimation = isNew ? nil : layoutAnimation.updateAnimation;
658+
BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew;
659+
RCTAnimation *createAnimation = shouldAnimateCreation ? layoutAnimation.createAnimation : nil;
660+
661+
BOOL isHidden = frameData.isHidden;
662+
if (view.isHidden != isHidden) {
663+
view.hidden = isHidden;
664+
}
671665

672-
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
673-
if (updateBlock) {
674-
updateBlock(self, _viewRegistry);
666+
void (^completion)(BOOL) = ^(BOOL finished) {
667+
completionsCalled++;
668+
if (layoutAnimation.callback && completionsCalled == count) {
669+
layoutAnimation.callback(@[@(finished)]);
670+
671+
// It's unsafe to call this callback more than once, so we nil it out here
672+
// to make sure that doesn't happen.
673+
layoutAnimation.callback = nil;
675674
}
676-
} withCompletionBlock:completion];
675+
};
676+
677+
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
678+
if (createAnimation) {
677679

678-
// Animate view update
679-
} else if (updateAnimation) {
680-
[updateAnimation performAnimations:^{
680+
// Animate view creation
681681
[view reactSetFrame:frame];
682682

683-
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
684-
if (updateBlock) {
685-
updateBlock(self, _viewRegistry);
686-
}
687-
} withCompletionBlock:completion];
683+
CATransform3D finalTransform = view.layer.transform;
684+
CGFloat finalOpacity = view.layer.opacity;
688685

689-
// Update without animation
690-
} else {
691-
[view reactSetFrame:frame];
686+
NSString *property = createAnimation.property;
687+
if ([property isEqualToString:@"scaleXY"]) {
688+
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
689+
} else if ([property isEqualToString:@"opacity"]) {
690+
view.layer.opacity = 0.0;
691+
} else {
692+
RCTLogError(@"Unsupported layout animation createConfig property %@",
693+
createAnimation.property);
694+
}
692695

693-
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
694-
if (updateBlock) {
695-
updateBlock(self, _viewRegistry);
696+
[createAnimation performAnimations:^{
697+
if ([property isEqualToString:@"scaleXY"]) {
698+
view.layer.transform = finalTransform;
699+
} else if ([property isEqualToString:@"opacity"]) {
700+
view.layer.opacity = finalOpacity;
701+
}
702+
if (updateBlock) {
703+
updateBlock(self, viewRegistry);
704+
}
705+
} withCompletionBlock:completion];
706+
707+
} else if (updateAnimation) {
708+
709+
// Animate view update
710+
[updateAnimation performAnimations:^{
711+
[view reactSetFrame:frame];
712+
if (updateBlock) {
713+
updateBlock(self, viewRegistry);
714+
}
715+
} withCompletionBlock:completion];
696716
}
697-
completion(YES);
698717
}
699718
}
700719

701-
_layoutAnimation = nil;
720+
// Clean up
721+
uiManager->_layoutAnimation = nil;
702722
};
703723
}
704724

@@ -802,10 +822,11 @@ - (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
802822
// the view events anyway.
803823
view.userInteractionEnabled = NO;
804824

825+
NSString *property = deleteAnimation.property;
805826
[deleteAnimation performAnimations:^{
806-
if ([deleteAnimation.property isEqual:@"scaleXY"]) {
827+
if ([property isEqualToString:@"scaleXY"]) {
807828
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
808-
} else if ([deleteAnimation.property isEqual:@"opacity"]) {
829+
} else if ([property isEqualToString:@"opacity"]) {
809830
view.layer.opacity = 0.0;
810831
} else {
811832
RCTLogError(@"Unsupported layout animation createConfig property %@",

0 commit comments

Comments
 (0)