diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index a045f30b28..fce8cd2198 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -277,6 +277,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _contentsScaleForDisplay = ASScreenScale(); _displaySentinel = [[ASSentinel alloc] init]; _preferredFrameSize = CGSizeZero; + _pendingViewState = [_ASPendingState new]; } - (id)init @@ -2358,15 +2359,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) #pragma mark - Pending View State -- (_ASPendingState *)pendingViewState -{ - if (!_pendingViewState) { - _pendingViewState = [[_ASPendingState alloc] init]; - ASDisplayNodeAssertNotNil(_pendingViewState, @"should have created a pendingViewState"); - } - - return _pendingViewState; -} - (void)_applyPendingStateToViewOrLayer { diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 6786483f14..d24b7d2066 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -16,6 +16,7 @@ #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" #import "ASEqualityHelpers.h" +#import "ASPendingStateController.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. @@ -42,11 +43,25 @@ #define _bridge_prologue () #endif -#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) __loaded ? \ - (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr))\ - : self.pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) +/// Returns YES if the property set should be applied to view/layer immediately. +ASDISPLAYNODE_INLINE BOOL ASDisplayNodeMarkDirtyIfNeeded(ASDisplayNode *node) { + if (NSThread.isMainThread) { + return node.nodeLoaded; + } else { + if (node.nodeLoaded && !node->_pendingViewState.hasChanges) { + [ASPendingStateController.sharedInstance registerNode:node]; + } + return NO; + } +}; -#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) __loaded ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : self.pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) +#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeMarkDirtyIfNeeded(self); \ + _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); \ + if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } + +#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeMarkDirtyIfNeeded(self); \ +_pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); \ +if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } #define _getFromPendingViewState(viewAndPendingViewStateProperty) _pendingViewState.viewAndPendingViewStateProperty @@ -217,7 +232,6 @@ // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif - _setToViewOnly(frame, rect); } else { // This is by far the common case / hot path. @@ -307,16 +321,10 @@ - (void)setOpaque:(BOOL)newOpaque { - BOOL prevOpaque = self.opaque; - _bridge_prologue; - if (prevOpaque != newOpaque) { - [self setNeedsDisplay]; - } + _setToViewOrLayer(opaque, newOpaque, opaque, newOpaque); - if (NSThread.isMainThread) { - _setToLayer(opaque, newOpaque); - } + // TODO: Mark as needs display if value changed? } - (BOOL)isUserInteractionEnabled diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 147323a61b..498b0ff0cc 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -50,6 +50,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @interface ASDisplayNode () { +@package + _ASPendingState *_pendingViewState; + @protected // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. ASDN::RecursiveMutex _propertyLock; @@ -92,8 +95,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // keeps track of nodes/subnodes that have not finished display, used with placeholders NSMutableSet *_pendingDisplayNodes; - - _ASPendingState *_pendingViewState; struct ASDisplayNodeFlags { // public properties diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 26141ae2e3..f7d8f6e52c 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -10,6 +10,7 @@ #import "ASThread.h" #import "ASWeakSet.h" #import "ASDisplayNode.h" +#import "ASAssert.h" @interface ASPendingStateController() { @@ -56,6 +57,7 @@ - (void)registerNode:(ASDisplayNode *)node { + ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node); ASDN::MutexLocker l(_lock); [_dirtyNodes addObject:node]; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 455591cd1b..e86c10484e 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -425,6 +425,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\ - (void)checkSimpleBridgePropertiesSetPropagate:(BOOL)isLayerBacked { + /// The first node we instantiate must be created on the main thread + /// in order to read ASScreenScale() safely. We create this throwaway + /// node so that running this test first in the suite + /// doesn't cause a deadlock. + [ASDisplayNode new]; + __block ASDisplayNode *node = nil; [self executeOffThread:^{ @@ -484,13 +490,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ [self checkValuesMatchSetValues:node isLayerBacked:isLayerBacked]; - // As a final sanity check, change a value on the realized view and ensure it is fetched through the node. - if (isLayerBacked) { - node.layer.hidden = NO; - } else { - node.view.hidden = NO; - } - XCTAssertEqual(NO, node.hidden, @"After the view is realized, the node should delegate properties to the view."); + // TODO: Handle backwards propagation i.e. from view/layer to node. } // Set each of the simple bridged UIView properties to a non-default value off-thread, then