Skip to content

Commit c858420

Browse files
ryangombaFacebook Github Bot
authored and
Facebook Github Bot
committed
Fix NativeAnimation invalidation & races on iOS
Summary: This diff attempts to fix a number of iOS native animation bugs related to improper node invalidation and a race with view creation. The major issues were presented in #9120 as problems 3 and 3b, but I'll recap here: The invalidation model we use is overly complicated and incomplete. The proper combination of `_needsUpdate` and `_hasUpdated` will result in nodes values being recomputed. However, we do not invalidate nodes in all the places we should, e.g. if we create a new view and attach it to an existing value node (see example in #9120). This diff chooses to remove the `_hasUpdated` flag, and simply relies on the `_needsUpdate` flag to mark a node as dirty. We mark nodes as dirty when they are: - created - updated - attached to new parents - detached from old parents - attached to a view Calling `updateNodeIfNecessary` will, if necessary, compute all invalidated parent values before recomputing the node value. It will then apply the update, and mark the no Closes #10663 Differential Revision: D4120301 Pulled By: mkonicek fbshipit-source-id: e247afcb5d8c15999b8328c664b9f7e764d76a75
1 parent bf901d9 commit c858420

18 files changed

+611
-388
lines changed

Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10+
#import <Foundation/Foundation.h>
1011
#import <CoreGraphics/CoreGraphics.h>
1112

1213
#import <React/RCTBridgeModule.h>
@@ -31,8 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
3132
- (void)stopAnimation;
3233
- (void)stepAnimation;
3334
- (void)removeAnimation;
34-
- (void)cleanupAnimationUpdate;
35-
36-
@end
3735

3836
NS_ASSUME_NONNULL_END
37+
38+
@end

Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m

-5
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,4 @@ - (void)updateOutputWithFrameOutput:(CGFloat)frameOutput
150150
[_valueNode setNeedsUpdate];
151151
}
152152

153-
- (void)cleanupAnimationUpdate
154-
{
155-
[_valueNode cleanupAnimationUpdate];
156-
}
157-
158153
@end

Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m

-5
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,4 @@ - (void)onUpdate:(CGFloat)outputValue
193193
[_valueNode setNeedsUpdate];
194194
}
195195

196-
- (void)cleanupAnimationUpdate
197-
{
198-
[_valueNode cleanupAnimationUpdate];
199-
}
200-
201196
@end

Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h

-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
@property (nonatomic, copy, readonly) NSDictionary<NSNumber *, RCTAnimatedNode *> *parentNodes;
2222

2323
@property (nonatomic, readonly) BOOL needsUpdate;
24-
@property (nonatomic, readonly) BOOL hasUpdated;
2524

2625
/**
2726
* Marks a node and its children as needing update.
@@ -38,11 +37,6 @@
3837
*/
3938
- (void)performUpdate NS_REQUIRES_SUPER;
4039

41-
/**
42-
* Cleans up after a round of updates.
43-
*/
44-
- (void)cleanupAnimationUpdate NS_REQUIRES_SUPER;
45-
4640
- (void)addChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
4741
- (void)removeChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
4842

Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m

+2-17
Original file line numberDiff line numberDiff line change
@@ -93,30 +93,15 @@ - (void)detachNode
9393

9494
- (void)setNeedsUpdate
9595
{
96-
if (_needsUpdate) {
97-
// Has already been marked. Stop branch.
98-
return;
99-
}
10096
_needsUpdate = YES;
10197
for (RCTAnimatedNode *child in _childNodes.allValues) {
10298
[child setNeedsUpdate];
10399
}
104100
}
105101

106-
- (void)cleanupAnimationUpdate
107-
{
108-
if (_hasUpdated) {
109-
_needsUpdate = NO;
110-
_hasUpdated = NO;
111-
for (RCTAnimatedNode *child in _childNodes.allValues) {
112-
[child cleanupAnimationUpdate];
113-
}
114-
}
115-
}
116-
117102
- (void)updateNodeIfNecessary
118103
{
119-
if (_needsUpdate && !_hasUpdated) {
104+
if (_needsUpdate) {
120105
for (RCTAnimatedNode *parent in _parentNodes.allValues) {
121106
[parent updateNodeIfNecessary];
122107
}
@@ -126,7 +111,7 @@ - (void)updateNodeIfNecessary
126111

127112
- (void)performUpdate
128113
{
129-
_hasUpdated = YES;
114+
_needsUpdate = NO;
130115
// To be overidden by subclasses
131116
// This method is called on a node only if it has been marked for update
132117
// during the current update loop

Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010
#import "RCTAnimatedNode.h"
1111

12-
@class RCTNativeAnimatedModule;
12+
@class RCTUIManager;
1313
@class RCTViewPropertyMapper;
1414

1515
@interface RCTPropsAnimatedNode : RCTAnimatedNode
1616

1717
@property (nonatomic, readonly) RCTViewPropertyMapper *propertyMapper;
1818

19-
- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule;
19+
- (void)connectToView:(NSNumber *)viewTag uiManager:(RCTUIManager *)uiManager;
2020
- (void)disconnectFromView:(NSNumber *)viewTag;
2121

2222
- (void)performViewUpdatesIfNecessary;

Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m

+32-25
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,17 @@
88
*/
99

1010
#import "RCTPropsAnimatedNode.h"
11+
1112
#import "RCTAnimationUtils.h"
12-
#import "RCTNativeAnimatedModule.h"
1313
#import "RCTStyleAnimatedNode.h"
14+
#import "RCTValueAnimatedNode.h"
1415
#import "RCTViewPropertyMapper.h"
1516

1617
@implementation RCTPropsAnimatedNode
17-
{
18-
RCTStyleAnimatedNode *_parentNode;
19-
}
20-
21-
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
22-
{
23-
[super onAttachedToNode:parent];
24-
if ([parent isKindOfClass:[RCTStyleAnimatedNode class]]) {
25-
_parentNode = (RCTStyleAnimatedNode *)parent;
26-
}
27-
}
28-
29-
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
30-
{
31-
[super onDetachedFromNode:parent];
32-
if (_parentNode == parent) {
33-
_parentNode = nil;
34-
}
35-
}
3618

37-
- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule
19+
- (void)connectToView:(NSNumber *)viewTag uiManager:(RCTUIManager *)uiManager
3820
{
39-
_propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag animationModule:animationModule];
21+
_propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag uiManager:uiManager];
4022
}
4123

4224
- (void)disconnectFromView:(NSNumber *)viewTag
@@ -50,11 +32,36 @@ - (void)performUpdate
5032
[self performViewUpdatesIfNecessary];
5133
}
5234

35+
- (NSString *)propertyNameForParentTag:(NSNumber *)parentTag
36+
{
37+
__block NSString *propertyName;
38+
[self.config[@"props"] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull property, NSNumber * _Nonnull tag, BOOL * _Nonnull stop) {
39+
if ([tag isEqualToNumber:parentTag]) {
40+
propertyName = property;
41+
*stop = YES;
42+
}
43+
}];
44+
return propertyName;
45+
}
46+
5347
- (void)performViewUpdatesIfNecessary
5448
{
55-
NSDictionary *updates = [_parentNode updatedPropsDictionary];
56-
if (updates.count) {
57-
[_propertyMapper updateViewWithDictionary:updates];
49+
NSMutableDictionary *props = [NSMutableDictionary dictionary];
50+
[self.parentNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull parentTag, RCTAnimatedNode * _Nonnull parentNode, BOOL * _Nonnull stop) {
51+
52+
if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) {
53+
[props addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
54+
55+
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
56+
NSString *property = [self propertyNameForParentTag:parentTag];
57+
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
58+
[props setObject:@(value) forKey:property];
59+
}
60+
61+
}];
62+
63+
if (props.count) {
64+
[_propertyMapper updateViewWithDictionary:props];
5865
}
5966
}
6067

Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111

1212
@interface RCTStyleAnimatedNode : RCTAnimatedNode
1313

14-
- (NSDictionary<NSString *, NSObject *> *)updatedPropsDictionary;
14+
- (NSDictionary<NSString *, NSObject *> *)propsDictionary;
1515

1616
@end

Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m

+7-13
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@
1414

1515
@implementation RCTStyleAnimatedNode
1616
{
17-
NSMutableDictionary<NSString *, NSObject *> *_updatedPropsDictionary;
17+
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
1818
}
1919

2020
- (instancetype)initWithTag:(NSNumber *)tag
2121
config:(NSDictionary<NSString *, id> *)config;
2222
{
2323
if ((self = [super initWithTag:tag config:config])) {
24-
_updatedPropsDictionary = [NSMutableDictionary new];
24+
_propsDictionary = [NSMutableDictionary new];
2525
}
2626
return self;
2727
}
2828

29-
- (NSDictionary *)updatedPropsDictionary
29+
- (NSDictionary *)propsDictionary
3030
{
31-
return _updatedPropsDictionary;
31+
return _propsDictionary;
3232
}
3333

3434
- (void)performUpdate
@@ -38,22 +38,16 @@ - (void)performUpdate
3838
NSDictionary<NSString *, NSNumber *> *style = self.config[@"style"];
3939
[style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
4040
RCTAnimatedNode *node = self.parentNodes[nodeTag];
41-
if (node && node.hasUpdated) {
41+
if (node) {
4242
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
4343
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
44-
[self->_updatedPropsDictionary setObject:@(parentNode.value) forKey:property];
44+
[self->_propsDictionary setObject:@(parentNode.value) forKey:property];
4545
} else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) {
4646
RCTTransformAnimatedNode *parentNode = (RCTTransformAnimatedNode *)node;
47-
[self->_updatedPropsDictionary addEntriesFromDictionary:parentNode.updatedPropsDictionary];
47+
[self->_propsDictionary addEntriesFromDictionary:parentNode.propsDictionary];
4848
}
4949
}
5050
}];
5151
}
5252

53-
- (void)cleanupAnimationUpdate
54-
{
55-
[super cleanupAnimationUpdate];
56-
[_updatedPropsDictionary removeAllObjects];
57-
}
58-
5953
@end

Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111

1212
@interface RCTTransformAnimatedNode : RCTAnimatedNode
1313

14-
- (NSDictionary<NSString *, NSObject *> *)updatedPropsDictionary;
14+
- (NSDictionary<NSString *, NSObject *> *)propsDictionary;
1515

1616
@end

Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m

+6-12
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@
1212

1313
@implementation RCTTransformAnimatedNode
1414
{
15-
NSMutableDictionary<NSString *, NSObject *> *_updatedPropsDictionary;
15+
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
1616
}
1717

1818
- (instancetype)initWithTag:(NSNumber *)tag
1919
config:(NSDictionary<NSString *, id> *)config;
2020
{
2121
if ((self = [super initWithTag:tag config:config])) {
22-
_updatedPropsDictionary = [NSMutableDictionary new];
22+
_propsDictionary = [NSMutableDictionary new];
2323
}
2424
return self;
2525
}
2626

27-
- (NSDictionary *)updatedPropsDictionary
27+
- (NSDictionary *)propsDictionary
2828
{
29-
return _updatedPropsDictionary;
29+
return _propsDictionary;
3030
}
3131

3232
- (void)performUpdate
@@ -44,7 +44,7 @@ - (void)performUpdate
4444
if ([type isEqualToString: @"animated"]) {
4545
NSNumber *nodeTag = transformConfig[@"nodeTag"];
4646
RCTAnimatedNode *node = self.parentNodes[nodeTag];
47-
if (!node.hasUpdated || ![node isKindOfClass:[RCTValueAnimatedNode class]]) {
47+
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
4848
continue;
4949
}
5050
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
@@ -82,13 +82,7 @@ - (void)performUpdate
8282
}
8383
}
8484

85-
_updatedPropsDictionary[@"transform"] = [NSValue valueWithCATransform3D:transform];
86-
}
87-
88-
- (void)cleanupAnimationUpdate
89-
{
90-
[super cleanupAnimationUpdate];
91-
[_updatedPropsDictionary removeAllObjects];
85+
_propsDictionary[@"transform"] = [NSValue valueWithCATransform3D:transform];
9286
}
9387

9488
@end

Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */; };
3939
944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */; };
4040
944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */; };
41+
9476E8EC1DC9232D005D5CD1 /* RCTNativeAnimatedNodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */; };
4142
94C129511D40692B0025F25C /* RCTFrameAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */; };
4243
94C129521D40692B0025F25C /* RCTSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */; };
44+
94DA09181DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */; };
4345
94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */; };
4446
/* End PBXBuildFile section */
4547

@@ -79,6 +81,8 @@
7981
94C1294D1D4069170025F25C /* RCTFrameAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTFrameAnimation.m; sourceTree = "<group>"; };
8082
94C1294E1D4069170025F25C /* RCTSpringAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTSpringAnimation.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
8183
94C1294F1D4069170025F25C /* RCTSpringAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTSpringAnimation.m; sourceTree = "<group>"; };
84+
94DA09161DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNativeAnimatedNodesManager.h; sourceTree = "<group>"; };
85+
94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNativeAnimatedNodesManager.m; sourceTree = "<group>"; };
8286
94DAE3F71D7334A70059942F /* RCTModuloAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTModuloAnimatedNode.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
8387
94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuloAnimatedNode.m; sourceTree = "<group>"; };
8488
/* End PBXFileReference section */
@@ -130,6 +134,8 @@
130134
13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */,
131135
13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */,
132136
13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */,
137+
94DA09161DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.h */,
138+
94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */,
133139
94C129491D4069170025F25C /* Drivers */,
134140
13E501D51D07A6C9005F35D8 /* Nodes */,
135141
134814211AA4EA7D00B7C361 /* Products */,
@@ -242,6 +248,7 @@
242248
2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */,
243249
944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */,
244250
944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */,
251+
9476E8EC1DC9232D005D5CD1 /* RCTNativeAnimatedNodesManager.m in Sources */,
245252
);
246253
runOnlyForDeploymentPostprocessing = 0;
247254
};
@@ -251,6 +258,7 @@
251258
files = (
252259
94C129511D40692B0025F25C /* RCTFrameAnimation.m in Sources */,
253260
94C129521D40692B0025F25C /* RCTSpringAnimation.m in Sources */,
261+
94DA09181DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m in Sources */,
254262
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */,
255263
94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */,
256264
193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */,

Libraries/NativeAnimation/RCTNativeAnimatedModule.h

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
9+
910
#import <React/RCTBridgeModule.h>
1011
#import <React/RCTEventDispatcher.h>
1112
#import <React/RCTEventEmitter.h>

0 commit comments

Comments
 (0)