Move prop diffing for Interop Layer to Adapter

Summary:
We have 1 coordinator per class but 1 adapter per instance.
Currently, the `oldProps` are stored in the coordinator and not into the adapter.
Therefore, when we create multiple instances of the same legacy component, the props might get messy or not updated properly.

This change moves the `oldProps` and the diffing code to the Adapter rather than to the coordinator, making them instance-specific.

## Changelog:
[iOS][Fixed] - Move old props and prop diffing to the interop layer adapter

Reviewed By: sammy-SC

Differential Revision: D52368222

fbshipit-source-id: 0f0c47b586fe61404250c5bfe51a7e2c63012815
This commit is contained in:
Riccardo Cipolleschi
2023-12-21 12:01:02 -08:00
committed by Facebook GitHub Bot
parent 06a053f54e
commit 3b80531f32
3 changed files with 98 additions and 94 deletions
@@ -6,11 +6,13 @@
*/
#import "RCTLegacyViewManagerInteropCoordinatorAdapter.h"
#import <React/RCTFollyConvert.h>
#import <React/UIView+React.h>
@implementation RCTLegacyViewManagerInteropCoordinatorAdapter {
RCTLegacyViewManagerInteropCoordinator *_coordinator;
NSInteger _tag;
NSDictionary<NSString *, id> *_oldProps;
}
- (instancetype)initWithCoordinator:(RCTLegacyViewManagerInteropCoordinator *)coordinator reactTag:(NSInteger)tag
@@ -47,7 +49,14 @@
- (void)setProps:(const folly::dynamic &)props
{
[_coordinator setProps:props forView:self.paperView];
if (props.isObject()) {
NSDictionary<NSString *, id> *convertedProps = facebook::react::convertFollyDynamicToId(props);
NSDictionary<NSString *, id> *diffedProps = [self _diffProps:convertedProps];
[_coordinator setProps:diffedProps forView:self.paperView];
_oldProps = convertedProps;
}
}
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
@@ -55,4 +64,87 @@
[_coordinator handleCommand:commandName args:args reactTag:_tag paperView:self.paperView];
}
- (NSDictionary<NSString *, id> *)_diffProps:(NSDictionary<NSString *, id> *)newProps
{
NSMutableDictionary<NSString *, id> *diffedProps = [NSMutableDictionary new];
[newProps enumerateKeysAndObjectsUsingBlock:^(NSString *key, id newProp, __unused BOOL *stop) {
id oldProp = _oldProps[key];
if ([self _prop:newProp isDifferentFrom:oldProp]) {
diffedProps[key] = newProp;
}
}];
return diffedProps;
}
#pragma mark - Private
- (BOOL)_prop:(id)oldProp isDifferentFrom:(id)newProp
{
// Check for JSON types.
// JSON types can be of:
// * number
// * bool
// * String
// * Array
// * Objects => Dictionaries in ObjectiveC
// * Null
// Check for NULL
BOOL bothNil = !oldProp && !newProp;
if (bothNil) {
return NO;
}
BOOL onlyOneNil = (oldProp && !newProp) || (!oldProp && newProp);
if (onlyOneNil) {
return YES;
}
if ([self _propIsSameNumber:oldProp second:newProp]) {
// Boolean should be captured by NSNumber
return NO;
}
if ([self _propIsSameString:oldProp second:newProp]) {
return NO;
}
if ([self _propIsSameArray:oldProp second:newProp]) {
return NO;
}
if ([self _propIsSameObject:oldProp second:newProp]) {
return NO;
}
// Previous behavior, fallback to YES
return YES;
}
- (BOOL)_propIsSameNumber:(id)first second:(id)second
{
return [first isKindOfClass:[NSNumber class]] && [second isKindOfClass:[NSNumber class]] &&
[(NSNumber *)first isEqualToNumber:(NSNumber *)second];
}
- (BOOL)_propIsSameString:(id)first second:(id)second
{
return [first isKindOfClass:[NSString class]] && [second isKindOfClass:[NSString class]] &&
[(NSString *)first isEqualToString:(NSString *)second];
}
- (BOOL)_propIsSameArray:(id)first second:(id)second
{
return [first isKindOfClass:[NSArray class]] && [second isKindOfClass:[NSArray class]] &&
[(NSArray *)first isEqualToArray:(NSArray *)second];
}
- (BOOL)_propIsSameObject:(id)first second:(id)second
{
return [first isKindOfClass:[NSDictionary class]] && [second isKindOfClass:[NSDictionary class]] &&
[(NSDictionary *)first isEqualToDictionary:(NSDictionary *)second];
}
@end
@@ -31,7 +31,7 @@ typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);
- (void)removeObserveForTag:(NSInteger)tag;
- (void)setProps:(const folly::dynamic &)props forView:(UIView *)view;
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(UIView *)view;
- (NSString *)componentViewName;
@@ -41,8 +41,6 @@ using namespace facebook::react;
*/
NSMutableArray<id<RCTBridgeMethod>> *_moduleMethods;
NSMutableDictionary<NSString *, id<RCTBridgeMethod>> *_moduleMethodsByName;
NSDictionary<NSString *, id> *_oldProps;
}
- (instancetype)initWithComponentData:(RCTComponentData *)componentData
@@ -98,17 +96,12 @@ using namespace facebook::react;
return view;
}
- (void)setProps:(const folly::dynamic &)props forView:(UIView *)view
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(UIView *)view
{
if (props.isObject()) {
NSDictionary<NSString *, id> *convertedProps = convertFollyDynamicToId(props);
NSDictionary<NSString *, id> *diffedProps = [self _diffProps:convertedProps];
[_componentData setProps:diffedProps forView:view];
[_componentData setProps:props forView:view];
if ([view respondsToSelector:@selector(didSetProps:)]) {
[view performSelector:@selector(didSetProps:) withObject:[diffedProps allKeys]];
}
_oldProps = convertedProps;
if ([view respondsToSelector:@selector(didSetProps:)]) {
[view performSelector:@selector(didSetProps:) withObject:[props allKeys]];
}
}
@@ -252,85 +245,4 @@ using namespace facebook::react;
}
}
- (NSDictionary<NSString *, id> *)_diffProps:(NSDictionary<NSString *, id> *)newProps
{
NSMutableDictionary<NSString *, id> *diffedProps = [NSMutableDictionary new];
[newProps enumerateKeysAndObjectsUsingBlock:^(NSString *key, id newProp, __unused BOOL *stop) {
id oldProp = _oldProps[key];
if ([self _prop:newProp isDifferentFrom:oldProp]) {
diffedProps[key] = newProp;
}
}];
return diffedProps;
}
- (BOOL)_prop:(id)oldProp isDifferentFrom:(id)newProp
{
// Check for JSON types.
// JSON types can be of:
// * number
// * bool
// * String
// * Array
// * Objects => Dictionaries in ObjectiveC
// * Null
// Check for NULL
BOOL bothNil = !oldProp && !newProp;
if (bothNil) {
return NO;
}
BOOL onlyOneNil = (oldProp && !newProp) || (!oldProp && newProp);
if (onlyOneNil) {
return YES;
}
if ([self _propIsSameNumber:oldProp second:newProp]) {
// Boolean should be captured by NSNumber
return NO;
}
if ([self _propIsSameString:oldProp second:newProp]) {
return NO;
}
if ([self _propIsSameArray:oldProp second:newProp]) {
return NO;
}
if ([self _propIsSameObject:oldProp second:newProp]) {
return NO;
}
// Previous behavior, fallback to YES
return YES;
}
- (BOOL)_propIsSameNumber:(id)first second:(id)second
{
return [first isKindOfClass:[NSNumber class]] && [second isKindOfClass:[NSNumber class]] &&
[(NSNumber *)first isEqualToNumber:(NSNumber *)second];
}
- (BOOL)_propIsSameString:(id)first second:(id)second
{
return [first isKindOfClass:[NSString class]] && [second isKindOfClass:[NSString class]] &&
[(NSString *)first isEqualToString:(NSString *)second];
}
- (BOOL)_propIsSameArray:(id)first second:(id)second
{
return [first isKindOfClass:[NSArray class]] && [second isKindOfClass:[NSArray class]] &&
[(NSArray *)first isEqualToArray:(NSArray *)second];
}
- (BOOL)_propIsSameObject:(id)first second:(id)second
{
return [first isKindOfClass:[NSDictionary class]] && [second isKindOfClass:[NSDictionary class]] &&
[(NSDictionary *)first isEqualToDictionary:(NSDictionary *)second];
}
@end