Skip to content

Commit

Permalink
Move prop diffing for Interop Layer to Adapter
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Dec 21, 2023
1 parent 06a053f commit 3b80531
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,12 +49,102 @@ - (UIView *)paperView

- (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
{
[_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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ @implementation RCTLegacyViewManagerInteropCoordinator {
*/
NSMutableArray<id<RCTBridgeMethod>> *_moduleMethods;
NSMutableDictionary<NSString *, id<RCTBridgeMethod>> *_moduleMethodsByName;

NSDictionary<NSString *, id> *_oldProps;
}

- (instancetype)initWithComponentData:(RCTComponentData *)componentData
Expand Down Expand Up @@ -98,17 +96,12 @@ - (UIView *)createPaperViewWithTag:(NSInteger)tag;
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]];
}
}

Expand Down Expand Up @@ -252,85 +245,4 @@ - (void)_lookupModuleMethodsIfNecessary
}
}

- (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

0 comments on commit 3b80531

Please sign in to comment.