Fix locking situation of layout transition (#3217)

- It's not safe to hold the lock of a supernode and:
  - Edit states of its subnodes
  - Call subclass hooks
  - Run completion block
  - Run animation, which can trigger layout passes on subnodes, especially if one of them is a collection view.
This commit is contained in:
Huy Nguyen
2017-03-24 17:35:43 +00:00
committed by Adlai Holler
parent ead2086f96
commit 0f50cd02c9

View File

@@ -1305,7 +1305,11 @@ ASLayoutElementFinalLayoutElementDefault
- (void)_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
{
ASDN::MutexLocker l(__instanceLock__);
[self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout];
}
- (void)_locked_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
{
ASDisplayNodeAssertTrue(displayNodeLayout->layout.layoutElement == self);
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.width >= 0.0);
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.height >= 0.0);
@@ -1313,6 +1317,7 @@ ASLayoutElementFinalLayoutElementDefault
_calculatedDisplayNodeLayout = displayNodeLayout;
}
- (CGSize)calculatedSize
{
ASDN::MutexLocker l(__instanceLock__);
@@ -1540,22 +1545,35 @@ ASLayoutElementFinalLayoutElementDefault
}
ASPerformBlockOnMainThread(^{
// Grab __instanceLock__ here to make sure this transition isn't invalidated
// right after it passed the validation test and before it proceeds
ASDN::MutexLocker l(__instanceLock__);
if ([self _shouldAbortTransitionWithID:transitionID]) {
return;
ASLayoutTransition *pendingLayoutTransition;
_ASTransitionContext *pendingLayoutTransitionContext;
{
// Grab __instanceLock__ here to make sure this transition isn't invalidated
// right after it passed the validation test and before it proceeds
ASDN::MutexLocker l(__instanceLock__);
if ([self _locked_shouldAbortTransitionWithID:transitionID]) {
return;
}
// Update calculated layout
auto previousLayout = _calculatedDisplayNodeLayout;
auto pendingLayout = std::make_shared<ASDisplayNodeLayout>(
newLayout,
constrainedSize,
constrainedSize.max
);
[self _locked_setCalculatedDisplayNodeLayout:pendingLayout];
// Setup pending layout transition for animation
_pendingLayoutTransition = pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
pendingLayout:pendingLayout
previousLayout:previousLayout];
// Setup context for pending layout transition. we need to hold a strong reference to the context
_pendingLayoutTransitionContext = pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
layoutDelegate:_pendingLayoutTransition
completionDelegate:self];
}
// Update calculated layout
auto previousLayout = _calculatedDisplayNodeLayout;
auto pendingLayout = std::make_shared<ASDisplayNodeLayout>(
newLayout,
constrainedSize,
constrainedSize.max
);
[self _setCalculatedDisplayNodeLayout:pendingLayout];
// Apply complete layout transitions for all subnodes
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
@@ -1570,20 +1588,11 @@ ASLayoutElementFinalLayoutElementDefault
completion();
}
// Setup pending layout transition for animation
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
pendingLayout:pendingLayout
previousLayout:previousLayout];
// Setup context for pending layout transition. we need to hold a strong reference to the context
_pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
layoutDelegate:_pendingLayoutTransition
completionDelegate:self];
// Apply the subnode insertion immediately to be able to animate the nodes
[_pendingLayoutTransition applySubnodeInsertions];
[pendingLayoutTransition applySubnodeInsertions];
// Kick off animating the layout transition
[self animateLayoutTransition:_pendingLayoutTransitionContext];
[self animateLayoutTransition:pendingLayoutTransitionContext];
// Mark transaction as finished
[self _finishOrCancelTransition];
@@ -1669,6 +1678,11 @@ ASLayoutElementFinalLayoutElementDefault
- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID
{
ASDN::MutexLocker l(__instanceLock__);
return [self _locked_shouldAbortTransitionWithID:transitionID];
}
- (BOOL)_locked_shouldAbortTransitionWithID:(int32_t)transitionID
{
return (!_transitionInProgress || _transitionID != transitionID);
}