Fix potential retain cycles in Animated iOS

Summary:
Fixes potential retain cycles detected by an internal fb tool.

```
First:

__NSDictionaryM
-> RCTPropsAnimatedNode
-> _parentNodes -> __NSDictionaryM
-> RCTStyleAnimatedNode
-> _childNodes -> __NSDictionaryM

Second:

RCTScrollView
-> _eventDispatcher -> RCTEventDispatcher
-> _observers -> __NSArrayM
-> RCTNativeAnimatedModule
-> _nodesManager -> RCTNativeAnimatedNodesManager
-> _uiManager -> RCTUIManager
-> _viewRegistry -> __NSDictionaryM
-> RCTScrollView
```

First fix:
Use weak map for parent and child nodes, strong refs are managed by RCTNativeAnimatedNodesManager

Second fix:
Make RCTEventDispatcher observers a weak array and make sure we don't keep strong refs to UIManager in RCTNativeAnimatedNodesManager and RCTPropsAnimatedNode.

Tested that native animations still work in UIExplorer

[IOS] [BUGFIX] [NativeAnimated] - Fix potential retain cycles in Animated iOS
Closes https://github.com/facebook/react-native/pull/16506

Differential Revision: D6126400

Pulled By: shergin

fbshipit-source-id: 1ac5083f8ab79a806305edc23ae4796ed428f78b
This commit is contained in:
Janic Duplessis 2017-10-23 13:16:24 -07:00 коммит произвёл Facebook Github Bot
Родитель 18364e95b7
Коммит c47759a9ae
12 изменённых файлов: 33 добавлений и 33 удалений

Просмотреть файл

@ -16,8 +16,8 @@
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]];
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] &&
[parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value + parent2.value;

Просмотреть файл

@ -17,8 +17,8 @@
@property (nonatomic, readonly) NSNumber *nodeTag;
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *config;
@property (nonatomic, copy, readonly) NSDictionary<NSNumber *, RCTAnimatedNode *> *childNodes;
@property (nonatomic, copy, readonly) NSDictionary<NSNumber *, RCTAnimatedNode *> *parentNodes;
@property (nonatomic, copy, readonly) NSMapTable<NSNumber *, RCTAnimatedNode *> *childNodes;
@property (nonatomic, copy, readonly) NSMapTable<NSNumber *, RCTAnimatedNode *> *parentNodes;
@property (nonatomic, readonly) BOOL needsUpdate;

Просмотреть файл

@ -13,8 +13,8 @@
@implementation RCTAnimatedNode
{
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_childNodes;
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_parentNodes;
NSMapTable<NSNumber *, RCTAnimatedNode *> *_childNodes;
NSMapTable<NSNumber *, RCTAnimatedNode *> *_parentNodes;
}
- (instancetype)initWithTag:(NSNumber *)tag
@ -29,12 +29,12 @@
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (NSDictionary<NSNumber *, RCTAnimatedNode *> *)childNodes
- (NSMapTable<NSNumber *, RCTAnimatedNode *> *)childNodes
{
return _childNodes;
}
- (NSDictionary<NSNumber *, RCTAnimatedNode *> *)parentNodes
- (NSMapTable<NSNumber *, RCTAnimatedNode *> *)parentNodes
{
return _parentNodes;
}
@ -42,10 +42,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)addChild:(RCTAnimatedNode *)child
{
if (!_childNodes) {
_childNodes = [NSMutableDictionary new];
_childNodes = [NSMapTable strongToWeakObjectsMapTable];
}
if (child) {
_childNodes[child.nodeTag] = child;
[_childNodes setObject:child forKey:child.nodeTag];
[child onAttachedToNode:self];
}
}
@ -64,10 +64,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
if (!_parentNodes) {
_parentNodes = [NSMutableDictionary new];
_parentNodes = [NSMapTable strongToWeakObjectsMapTable];
}
if (parent) {
_parentNodes[parent.nodeTag] = parent;
[_parentNodes setObject:parent forKey:parent.nodeTag];
}
}
@ -83,10 +83,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)detachNode
{
for (RCTAnimatedNode *parent in _parentNodes.allValues) {
for (RCTAnimatedNode *parent in _parentNodes.objectEnumerator) {
[parent removeChild:self];
}
for (RCTAnimatedNode *child in _childNodes.allValues) {
for (RCTAnimatedNode *child in _childNodes.objectEnumerator) {
[self removeChild:child];
}
}
@ -94,7 +94,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)setNeedsUpdate
{
_needsUpdate = YES;
for (RCTAnimatedNode *child in _childNodes.allValues) {
for (RCTAnimatedNode *child in _childNodes.objectEnumerator) {
[child setNeedsUpdate];
}
}
@ -102,7 +102,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)updateNodeIfNecessary
{
if (_needsUpdate) {
for (RCTAnimatedNode *parent in _parentNodes.allValues) {
for (RCTAnimatedNode *parent in _parentNodes.objectEnumerator) {
[parent updateNodeIfNecessary];
}
[self performUpdate];

Просмотреть файл

@ -51,7 +51,7 @@
- (CGFloat)inputNodeValue
{
RCTValueAnimatedNode *inputNode = (RCTValueAnimatedNode *)self.parentNodes[_inputNodeTag];
RCTValueAnimatedNode *inputNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:_inputNodeTag];
if (![inputNode isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Illegal node ID set as an input for Animated.DiffClamp node");
return 0;

Просмотреть файл

@ -19,8 +19,8 @@
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]];
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] &&
[parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
if (parent2.value == 0) {

Просмотреть файл

@ -16,7 +16,7 @@
[super performUpdate];
NSNumber *inputNode = self.config[@"input"];
NSNumber *modulus = self.config[@"modulus"];
RCTValueAnimatedNode *parent = (RCTValueAnimatedNode *)self.parentNodes[inputNode];
RCTValueAnimatedNode *parent = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNode];
self.value = fmodf(parent.value, modulus.floatValue);
}

Просмотреть файл

@ -17,8 +17,8 @@
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]];
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] &&
[parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value * parent2.value;

Просмотреть файл

@ -20,7 +20,7 @@
{
NSNumber *_connectedViewTag;
NSString *_connectedViewName;
RCTUIManager *_uiManager;
__weak RCTUIManager *_uiManager;
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
}
@ -85,18 +85,18 @@
if (!_connectedViewTag) {
return;
}
[self.parentNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull parentTag, RCTAnimatedNode *_Nonnull parentNode, BOOL *_Nonnull stop) {
for (NSNumber *parentTag in self.parentNodes.keyEnumerator) {
RCTAnimatedNode *parentNode = [self.parentNodes objectForKey:parentTag];
if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) {
[self->_propsDictionary addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
NSString *property = [self propertyNameForParentTag:parentTag];
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
self->_propsDictionary[property] = @(value);
}
}];
}
if (_propsDictionary.count) {
[_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag

Просмотреть файл

@ -37,7 +37,7 @@
NSDictionary<NSString *, NSNumber *> *style = self.config[@"style"];
[style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
RCTAnimatedNode *node = self.parentNodes[nodeTag];
RCTAnimatedNode *node = [self.parentNodes objectForKey:nodeTag];
if (node) {
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;

Просмотреть файл

@ -41,7 +41,7 @@
NSNumber *value;
if ([type isEqualToString: @"animated"]) {
NSNumber *nodeTag = transformConfig[@"nodeTag"];
RCTAnimatedNode *node = self.parentNodes[nodeTag];
RCTAnimatedNode *node = [self.parentNodes objectForKey:nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
continue;
}

Просмотреть файл

@ -30,7 +30,7 @@
@implementation RCTNativeAnimatedNodesManager
{
RCTUIManager *_uiManager;
__weak RCTUIManager *_uiManager;
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
// Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time
// there will be only one driver per mapping so all code code should be optimized around that.

Просмотреть файл

@ -46,7 +46,7 @@ static NSNumber *RCTGetEventID(id<RCTEvent> event)
// This array contains ids of events in order they come in, so we can emit them to JS in the exact same order.
NSMutableArray<NSNumber *> *_eventQueue;
BOOL _eventsDispatchScheduled;
NSMutableArray<id<RCTEventDispatcherObserver>> *_observers;
NSHashTable<id<RCTEventDispatcherObserver>> *_observers;
NSLock *_observersLock;
}
@ -61,7 +61,7 @@ RCT_EXPORT_MODULE()
_eventQueue = [NSMutableArray new];
_eventQueueLock = [NSLock new];
_eventsDispatchScheduled = NO;
_observers = [NSMutableArray new];
_observers = [NSHashTable weakObjectsHashTable];
_observersLock = [NSLock new];
}