mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-04-29 12:15:36 +08:00
Merge pull request #1256 from Adlai-Holler/AHAffinityRevisions
[ASDisplayNode] Post-Review Refinements of Recent No-Affinity UIKit/CA Property Bridging
This commit is contained in:
@@ -79,6 +79,17 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
|
|||||||
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
|
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node)
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(node->_propertyLock);
|
||||||
|
_ASPendingState *result = node->_pendingViewState;
|
||||||
|
if (result == nil) {
|
||||||
|
result = [[_ASPendingState alloc] init];
|
||||||
|
node->_pendingViewState = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns ASDisplayNodeFlags for the givern class/instance. instance MAY BE NIL.
|
* Returns ASDisplayNodeFlags for the givern class/instance. instance MAY BE NIL.
|
||||||
*
|
*
|
||||||
@@ -257,7 +268,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
_contentsScaleForDisplay = ASScreenScale();
|
_contentsScaleForDisplay = ASScreenScale();
|
||||||
_displaySentinel = [[ASSentinel alloc] init];
|
_displaySentinel = [[ASSentinel alloc] init];
|
||||||
_preferredFrameSize = CGSizeZero;
|
_preferredFrameSize = CGSizeZero;
|
||||||
_pendingViewState = [_ASPendingState new];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)init
|
- (id)init
|
||||||
@@ -1037,11 +1047,6 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not rasterized (and therefore we certainly have a view or layer),
|
|
||||||
// Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded.
|
|
||||||
// Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay,
|
|
||||||
// as they don't need to display in the working range at all - since at all times onscreen, one
|
|
||||||
// -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame.
|
|
||||||
- (void)__setNeedsDisplay
|
- (void)__setNeedsDisplay
|
||||||
{
|
{
|
||||||
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
|
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
|
||||||
@@ -1894,6 +1899,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
|||||||
|
|
||||||
- (ASSizeRange)constrainedSizeForCalculatedLayout
|
- (ASSizeRange)constrainedSizeForCalculatedLayout
|
||||||
{
|
{
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
return _constrainedSize;
|
return _constrainedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
if (!ASObjectIsEqual(_image, image)) {
|
if (!ASObjectIsEqual(_image, image)) {
|
||||||
_image = image;
|
_image = image;
|
||||||
|
|
||||||
|
ASDN::MutexUnlocker u(_imageLock);
|
||||||
[self invalidateCalculatedLayout];
|
[self invalidateCalculatedLayout];
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
#define DISPLAYNODE_USE_LOCKS 1
|
#define DISPLAYNODE_USE_LOCKS 1
|
||||||
|
|
||||||
#define __loaded (_layer != nil)
|
#define __loaded(node) (node->_view != nil || (node->_layer != nil && node->_flags.layerBacked))
|
||||||
|
|
||||||
#if DISPLAYNODE_USE_LOCKS
|
#if DISPLAYNODE_USE_LOCKS
|
||||||
#define _bridge_prologue_read ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssertThreadAffinity(self)
|
#define _bridge_prologue_read ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssertThreadAffinity(self)
|
||||||
@@ -52,37 +52,36 @@
|
|||||||
/// the property cannot be immediately applied and the node does not already have pending changes.
|
/// the property cannot be immediately applied and the node does not already have pending changes.
|
||||||
/// This function must be called with the node's lock already held (after _bridge_prologue_write).
|
/// This function must be called with the node's lock already held (after _bridge_prologue_write).
|
||||||
ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) {
|
ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) {
|
||||||
|
BOOL loaded = __loaded(node);
|
||||||
if (ASDisplayNodeThreadIsMain()) {
|
if (ASDisplayNodeThreadIsMain()) {
|
||||||
return node.nodeLoaded;
|
return loaded;
|
||||||
} else {
|
} else {
|
||||||
if (node.nodeLoaded && !node->_pendingViewState.hasChanges) {
|
if (loaded && !node->_pendingViewState.hasChanges) {
|
||||||
[ASPendingStateController.sharedInstance registerNode:node];
|
[[ASPendingStateController sharedInstance] registerNode:node];
|
||||||
}
|
}
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded ? \
|
#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded(self) ? \
|
||||||
(_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\
|
(_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\
|
||||||
: self.pendingViewState.viewAndPendingViewStateProperty
|
: ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty
|
||||||
|
|
||||||
#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \
|
#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \
|
||||||
if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } else { _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); }
|
if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); }
|
||||||
|
|
||||||
#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \
|
#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \
|
||||||
if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); }
|
if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); }
|
||||||
|
|
||||||
#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded ? _view.viewAndPendingViewStateProperty : self.pendingViewState.viewAndPendingViewStateProperty
|
#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded(self) ? _view.viewAndPendingViewStateProperty : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty
|
||||||
|
|
||||||
#define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : self.pendingViewState.layerProperty
|
#define _getFromLayer(layerProperty) __loaded(self) ? _layer.layerProperty : ASDisplayNodeGetPendingState(self).layerProperty
|
||||||
|
|
||||||
#define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \
|
#define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \
|
||||||
if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingViewState.layerProperty = (layerValueExpr); }
|
if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNodeGetPendingState(self).layerProperty = (layerValueExpr); }
|
||||||
|
|
||||||
#define _messageToViewOrLayer(viewAndLayerSelector) (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector])
|
#define _messageToViewOrLayer(viewAndLayerSelector) (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector])
|
||||||
|
|
||||||
#define _messageToLayer(layerSelector) __loaded ? [_layer layerSelector] : [self.pendingViewState layerSelector]
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node,
|
* This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node,
|
||||||
* with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created.
|
* with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created.
|
||||||
@@ -235,36 +234,63 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie
|
|||||||
- (void)setFrame:(CGRect)rect
|
- (void)setFrame:(CGRect)rect
|
||||||
{
|
{
|
||||||
_bridge_prologue_write;
|
_bridge_prologue_write;
|
||||||
BOOL setFrameDirectly = _flags.synchronous && !_flags.layerBacked;
|
|
||||||
BOOL isMainThread = ASDisplayNodeThreadIsMain();
|
|
||||||
BOOL nodeLoaded = self.nodeLoaded;
|
|
||||||
if (nodeLoaded && isMainThread && setFrameDirectly) {
|
|
||||||
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
|
||||||
|
|
||||||
// Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform
|
|
||||||
#if DEBUG
|
|
||||||
// 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
|
|
||||||
_view.frame = rect;
|
|
||||||
} else if (!nodeLoaded || (isMainThread && !setFrameDirectly)) {
|
|
||||||
/**
|
|
||||||
* Sets a new frame to this node by changing its bounds and position. This method can be safely called even if
|
|
||||||
* the transform is a non-identity transform, because bounds and position can be set instead of frame.
|
|
||||||
* This is NOT called for synchronous nodes (wrapping regular views), which may rely on a [UIView setFrame:] call.
|
|
||||||
* A notable example of the latter is UITableView, which won't resize its internal container if only layer bounds are set.
|
|
||||||
*/
|
|
||||||
CGRect bounds;
|
|
||||||
CGPoint position;
|
|
||||||
ASBoundsAndPositionForFrame(rect, self.bounds.origin, self.anchorPoint, &bounds, &position);
|
|
||||||
|
|
||||||
self.bounds = bounds;
|
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
||||||
self.position = position;
|
struct ASDisplayNodeFlags flags = _flags;
|
||||||
} else if (nodeLoaded && !isMainThread) {
|
BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked;
|
||||||
if (!_pendingViewState.hasChanges) {
|
|
||||||
[ASPendingStateController.sharedInstance registerNode:self];
|
BOOL nodeLoaded = __loaded(self);
|
||||||
|
BOOL isMainThread = ASDisplayNodeThreadIsMain();
|
||||||
|
if (!setFrameDirectly) {
|
||||||
|
BOOL canReadProperties = isMainThread || !nodeLoaded;
|
||||||
|
if (canReadProperties) {
|
||||||
|
// We don't have to set frame directly, and we can read current properties.
|
||||||
|
// Compute a new bounds and position and set them on self.
|
||||||
|
CALayer *layer = _layer;
|
||||||
|
BOOL useLayer = (layer != nil);
|
||||||
|
CGPoint origin = (useLayer ? layer.bounds.origin : self.bounds.origin);
|
||||||
|
CGPoint anchorPoint = (useLayer ? layer.anchorPoint : self.anchorPoint);
|
||||||
|
|
||||||
|
CGRect newBounds = CGRectZero;
|
||||||
|
CGPoint newPosition = CGPointZero;
|
||||||
|
ASBoundsAndPositionForFrame(rect, origin, anchorPoint, &newBounds, &newPosition);
|
||||||
|
|
||||||
|
if (useLayer) {
|
||||||
|
layer.bounds = newBounds;
|
||||||
|
layer.position = newPosition;
|
||||||
|
} else {
|
||||||
|
self.bounds = newBounds;
|
||||||
|
self.position = newPosition;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't have to set frame directly, but we can't read properties.
|
||||||
|
// Store the frame in our pending state, and it'll get decomposed into
|
||||||
|
// bounds and position when the pending state is applied.
|
||||||
|
_ASPendingState *pendingState = ASDisplayNodeGetPendingState(self);
|
||||||
|
if (nodeLoaded && !pendingState.hasChanges) {
|
||||||
|
[[ASPendingStateController sharedInstance] registerNode:self];
|
||||||
|
}
|
||||||
|
pendingState.frame = rect;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (nodeLoaded && isMainThread) {
|
||||||
|
// We do have to set frame directly, and we're on main thread with a loaded node.
|
||||||
|
// Just set the frame on the view.
|
||||||
|
// NOTE: Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform.
|
||||||
|
#if DEBUG
|
||||||
|
// 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
|
||||||
|
_view.frame = rect;
|
||||||
|
} else {
|
||||||
|
// We do have to set frame directly, but either the node isn't loaded or we're on a non-main thread.
|
||||||
|
// Set the frame on the pending state, and it'll call setFrame: when applied.
|
||||||
|
_ASPendingState *pendingState = ASDisplayNodeGetPendingState(self);
|
||||||
|
if (nodeLoaded && !pendingState.hasChanges) {
|
||||||
|
[[ASPendingStateController sharedInstance] registerNode:self];
|
||||||
|
}
|
||||||
|
pendingState.frame = rect;
|
||||||
}
|
}
|
||||||
_pendingViewState.frame = rect;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,39 +314,37 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie
|
|||||||
[rasterizedContainerNode setNeedsDisplay];
|
[rasterizedContainerNode setNeedsDisplay];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (self.nodeLoaded) {
|
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
|
||||||
if (ASDisplayNodeThreadIsMain()) {
|
if (shouldApply) {
|
||||||
_messageToViewOrLayer(setNeedsDisplay);
|
// If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a
|
||||||
} else {
|
// message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay,
|
||||||
if (!_pendingViewState.hasChanges) {
|
// which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared.
|
||||||
[ASPendingStateController.sharedInstance registerNode:self];
|
_messageToViewOrLayer(setNeedsDisplay);
|
||||||
}
|
|
||||||
[self __setNeedsDisplay];
|
|
||||||
[_pendingViewState setNeedsDisplay];
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
[self __setNeedsDisplay];
|
[ASDisplayNodeGetPendingState(self) setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
[self __setNeedsDisplay];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setNeedsLayout
|
- (void)setNeedsLayout
|
||||||
{
|
{
|
||||||
_bridge_prologue_write;
|
_bridge_prologue_write;
|
||||||
if (self.nodeLoaded) {
|
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
|
||||||
if (ASDisplayNodeThreadIsMain()) {
|
if (shouldApply) {
|
||||||
[self __setNeedsLayout];
|
// The node is loaded and we're on main.
|
||||||
_messageToViewOrLayer(setNeedsLayout);
|
// Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging
|
||||||
} else {
|
// the view or layer to ensure that measurement and implicitly added subnodes have been handled.
|
||||||
if (!_pendingViewState.hasChanges) {
|
[self __setNeedsLayout];
|
||||||
[ASPendingStateController.sharedInstance registerNode:self];
|
_messageToViewOrLayer(setNeedsLayout);
|
||||||
}
|
} else if (__loaded(self)) {
|
||||||
// NOTE: We will call [self __setNeedsLayout] just before we apply
|
// The node is loaded but we're not on main.
|
||||||
// the pending state. We need to call it on main if the node is loaded
|
// We will call [self __setNeedsLayout] when we apply
|
||||||
// to support implicit hierarchy management.
|
// the pending state. We need to call it on main if the node is loaded
|
||||||
[_pendingViewState setNeedsLayout];
|
// to support implicit hierarchy management.
|
||||||
}
|
[ASDisplayNodeGetPendingState(self) setNeedsLayout];
|
||||||
} else {
|
} else {
|
||||||
|
// The node is not loaded and we're not on main.
|
||||||
[self __setNeedsLayout];
|
[self __setNeedsLayout];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,28 +528,29 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie
|
|||||||
- (UIViewContentMode)contentMode
|
- (UIViewContentMode)contentMode
|
||||||
{
|
{
|
||||||
_bridge_prologue_read;
|
_bridge_prologue_read;
|
||||||
if (__loaded) {
|
if (__loaded(self)) {
|
||||||
if (_flags.layerBacked) {
|
if (_flags.layerBacked) {
|
||||||
return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity);
|
return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity);
|
||||||
} else {
|
} else {
|
||||||
return _view.contentMode;
|
return _view.contentMode;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return self.pendingViewState.contentMode;
|
return ASDisplayNodeGetPendingState(self).contentMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setContentMode:(UIViewContentMode)contentMode
|
- (void)setContentMode:(UIViewContentMode)contentMode
|
||||||
{
|
{
|
||||||
_bridge_prologue_write;
|
_bridge_prologue_write;
|
||||||
if (__loaded) {
|
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
|
||||||
|
if (shouldApply) {
|
||||||
if (_flags.layerBacked) {
|
if (_flags.layerBacked) {
|
||||||
_layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode);
|
_layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode);
|
||||||
} else {
|
} else {
|
||||||
_view.contentMode = contentMode;
|
_view.contentMode = contentMode;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.pendingViewState.contentMode = contentMode;
|
ASDisplayNodeGetPendingState(self).contentMode = contentMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,13 @@
|
|||||||
|
|
||||||
@protocol _ASDisplayLayerDelegate;
|
@protocol _ASDisplayLayerDelegate;
|
||||||
@class _ASDisplayLayer;
|
@class _ASDisplayLayer;
|
||||||
|
@class _ASPendingState;
|
||||||
|
|
||||||
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
|
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
|
||||||
|
|
||||||
|
/// Get the pending view state for the node, creating one if needed.
|
||||||
|
_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node);
|
||||||
|
|
||||||
typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||||
{
|
{
|
||||||
ASDisplayNodeMethodOverrideNone = 0,
|
ASDisplayNodeMethodOverrideNone = 0,
|
||||||
@@ -36,7 +40,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
|||||||
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4
|
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4
|
||||||
};
|
};
|
||||||
|
|
||||||
@class _ASPendingState;
|
|
||||||
@class _ASDisplayNodePosition;
|
@class _ASDisplayNodePosition;
|
||||||
|
|
||||||
FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification;
|
FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification;
|
||||||
@@ -52,10 +55,37 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
@package
|
@package
|
||||||
_ASPendingState *_pendingViewState;
|
_ASPendingState *_pendingViewState;
|
||||||
|
|
||||||
@protected
|
|
||||||
// Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads.
|
// Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads.
|
||||||
ASDN::RecursiveMutex _propertyLock;
|
ASDN::RecursiveMutex _propertyLock;
|
||||||
|
UIView *_view;
|
||||||
|
CALayer *_layer;
|
||||||
|
|
||||||
|
struct ASDisplayNodeFlags {
|
||||||
|
// public properties
|
||||||
|
unsigned synchronous:1;
|
||||||
|
unsigned layerBacked:1;
|
||||||
|
unsigned displaysAsynchronously:1;
|
||||||
|
unsigned shouldRasterizeDescendants:1;
|
||||||
|
unsigned shouldBypassEnsureDisplay:1;
|
||||||
|
unsigned displaySuspended:1;
|
||||||
|
unsigned hasCustomDrawingPriority:1;
|
||||||
|
|
||||||
|
// whether custom drawing is enabled
|
||||||
|
unsigned implementsInstanceDrawRect:1;
|
||||||
|
unsigned implementsDrawRect:1;
|
||||||
|
unsigned implementsInstanceImageDisplay:1;
|
||||||
|
unsigned implementsImageDisplay:1;
|
||||||
|
unsigned implementsDrawParameters:1;
|
||||||
|
|
||||||
|
// internal state
|
||||||
|
unsigned isMeasured:1;
|
||||||
|
unsigned isEnteringHierarchy:1;
|
||||||
|
unsigned isExitingHierarchy:1;
|
||||||
|
unsigned isInHierarchy:1;
|
||||||
|
unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS;
|
||||||
|
} _flags;
|
||||||
|
|
||||||
|
@protected
|
||||||
ASDisplayNode * __weak _supernode;
|
ASDisplayNode * __weak _supernode;
|
||||||
|
|
||||||
ASSentinel *_displaySentinel;
|
ASSentinel *_displaySentinel;
|
||||||
@@ -86,39 +116,12 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
ASDisplayNodeDidLoadBlock _nodeLoadedBlock;
|
ASDisplayNodeDidLoadBlock _nodeLoadedBlock;
|
||||||
Class _viewClass;
|
Class _viewClass;
|
||||||
Class _layerClass;
|
Class _layerClass;
|
||||||
UIView *_view;
|
|
||||||
CALayer *_layer;
|
|
||||||
|
|
||||||
UIImage *_placeholderImage;
|
UIImage *_placeholderImage;
|
||||||
CALayer *_placeholderLayer;
|
CALayer *_placeholderLayer;
|
||||||
|
|
||||||
// keeps track of nodes/subnodes that have not finished display, used with placeholders
|
// keeps track of nodes/subnodes that have not finished display, used with placeholders
|
||||||
NSMutableSet *_pendingDisplayNodes;
|
NSMutableSet *_pendingDisplayNodes;
|
||||||
|
|
||||||
struct ASDisplayNodeFlags {
|
|
||||||
// public properties
|
|
||||||
unsigned synchronous:1;
|
|
||||||
unsigned layerBacked:1;
|
|
||||||
unsigned displaysAsynchronously:1;
|
|
||||||
unsigned shouldRasterizeDescendants:1;
|
|
||||||
unsigned shouldBypassEnsureDisplay:1;
|
|
||||||
unsigned displaySuspended:1;
|
|
||||||
unsigned hasCustomDrawingPriority:1;
|
|
||||||
|
|
||||||
// whether custom drawing is enabled
|
|
||||||
unsigned implementsInstanceDrawRect:1;
|
|
||||||
unsigned implementsDrawRect:1;
|
|
||||||
unsigned implementsInstanceImageDisplay:1;
|
|
||||||
unsigned implementsImageDisplay:1;
|
|
||||||
unsigned implementsDrawParameters:1;
|
|
||||||
|
|
||||||
// internal state
|
|
||||||
unsigned isMeasured:1;
|
|
||||||
unsigned isEnteringHierarchy:1;
|
|
||||||
unsigned isExitingHierarchy:1;
|
|
||||||
unsigned isInHierarchy:1;
|
|
||||||
unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS;
|
|
||||||
} _flags;
|
|
||||||
|
|
||||||
ASDisplayNodeExtraIvars _extra;
|
ASDisplayNodeExtraIvars _extra;
|
||||||
|
|
||||||
@@ -139,9 +142,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
// The _ASDisplayLayer backing the node, if any.
|
// The _ASDisplayLayer backing the node, if any.
|
||||||
@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer;
|
@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer;
|
||||||
|
|
||||||
// Creates a pendingViewState if one doesn't exist. Allows setting view properties on a bg thread before there is a view.
|
|
||||||
@property (atomic, retain, readonly) _ASPendingState *pendingViewState;
|
|
||||||
|
|
||||||
// Bitmask to check which methods an object overrides.
|
// Bitmask to check which methods an object overrides.
|
||||||
@property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides;
|
@property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides;
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation,
|
|||||||
|
|
||||||
ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position)
|
ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position)
|
||||||
{
|
{
|
||||||
*bounds = (CGRect){ origin, rect.size };
|
*bounds = (CGRect){ origin, rect.size };
|
||||||
*position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
|
*position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
|
||||||
rect.origin.y + rect.size.height * anchorPoint.y);
|
rect.origin.y + rect.size.height * anchorPoint.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface NSIndexPath (ASInverseComparison)
|
@interface NSIndexPath (ASInverseComparison)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
_dirtyNodes = [ASWeakSet new];
|
_dirtyNodes = [[ASWeakSet alloc] init];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -40,21 +40,15 @@
|
|||||||
+ (ASPendingStateController *)sharedInstance
|
+ (ASPendingStateController *)sharedInstance
|
||||||
{
|
{
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
static ASPendingStateController *controller;
|
static ASPendingStateController *controller = nil;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
controller = [ASPendingStateController new];
|
controller = [[ASPendingStateController alloc] init];
|
||||||
});
|
});
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark External API
|
#pragma mark External API
|
||||||
|
|
||||||
- (void)flush
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertMainThread();
|
|
||||||
[self flushNow];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)registerNode:(ASDisplayNode *)node
|
- (void)registerNode:(ASDisplayNode *)node
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node);
|
ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node);
|
||||||
@@ -64,6 +58,25 @@
|
|||||||
[self scheduleFlushIfNeeded];
|
[self scheduleFlushIfNeeded];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: There is a small re-entrancy hazard here.
|
||||||
|
* If the user gives us a subclass of UIView/CALayer that
|
||||||
|
* adds side-effects to property sets, and one side effect
|
||||||
|
* waits on a background thread that sets a view/layer property
|
||||||
|
* on a loaded node, then we've got a deadlock.
|
||||||
|
*/
|
||||||
|
- (void)flush
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
ASDN::MutexLocker l(_lock);
|
||||||
|
for (ASDisplayNode *node in _dirtyNodes) {
|
||||||
|
[node applyPendingViewState];
|
||||||
|
}
|
||||||
|
[_dirtyNodes removeAllObjects];
|
||||||
|
_flags.pendingFlush = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Private Methods
|
#pragma mark Private Methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,27 +90,10 @@
|
|||||||
|
|
||||||
_flags.pendingFlush = YES;
|
_flags.pendingFlush = YES;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self flushNow];
|
[self flush];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: There is a small re-entrancy hazard here.
|
|
||||||
* If the user gives us a subclass of UIView/CALayer that
|
|
||||||
* adds side-effects to property sets, and one side effect
|
|
||||||
* waits on a background thread that sets a view/layer property
|
|
||||||
* on a loaded node, then we've got a deadlock.
|
|
||||||
*/
|
|
||||||
- (void)flushNow
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(_lock);
|
|
||||||
for (ASDisplayNode *node in _dirtyNodes) {
|
|
||||||
[node applyPendingViewState];
|
|
||||||
}
|
|
||||||
[_dirtyNodes removeAllObjects];
|
|
||||||
_flags.pendingFlush = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation ASPendingStateController (Testing)
|
@implementation ASPendingStateController (Testing)
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
#import "ASInternalHelpers.h"
|
#import "ASInternalHelpers.h"
|
||||||
#import "ASDisplayNodeInternal.h"
|
#import "ASDisplayNodeInternal.h"
|
||||||
|
|
||||||
|
#define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \
|
||||||
|
|| (flags.setOpaque && opaque != (layer).opaque)\
|
||||||
|
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, (layer).backgroundColor)))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Properties
|
// Properties
|
||||||
int needsDisplay:1;
|
int needsDisplay:1;
|
||||||
@@ -106,6 +110,30 @@ typedef struct {
|
|||||||
ASPendingStateFlags _flags;
|
ASPendingStateFlags _flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the state's frame, bounds, and position to layer. This will not
|
||||||
|
* be called on synchronous view-backed nodes which require we directly
|
||||||
|
* call [view setFrame:].
|
||||||
|
*
|
||||||
|
* FIXME: How should we reconcile order-of-operations between setting frame, bounds, position?
|
||||||
|
* Note we can't read bounds and position in the background, so we have to keep the frame
|
||||||
|
* value intact until application time (now).
|
||||||
|
*/
|
||||||
|
ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *state, CALayer *layer) {
|
||||||
|
ASPendingStateFlags flags = state->_flags;
|
||||||
|
if (flags.setFrame) {
|
||||||
|
CGRect _bounds = CGRectZero;
|
||||||
|
CGPoint _position = CGPointZero;
|
||||||
|
ASBoundsAndPositionForFrame(state->frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position);
|
||||||
|
layer.bounds = _bounds;
|
||||||
|
layer.position = _position;
|
||||||
|
} else {
|
||||||
|
if (flags.setBounds)
|
||||||
|
layer.bounds = state->bounds;
|
||||||
|
if (flags.setPosition)
|
||||||
|
layer.position = state->position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@synthesize clipsToBounds=clipsToBounds;
|
@synthesize clipsToBounds=clipsToBounds;
|
||||||
@synthesize opaque=opaque;
|
@synthesize opaque=opaque;
|
||||||
@@ -560,9 +588,7 @@ static UIColor *defaultTintColor = nil;
|
|||||||
{
|
{
|
||||||
ASPendingStateFlags flags = _flags;
|
ASPendingStateFlags flags = _flags;
|
||||||
|
|
||||||
if (flags.needsDisplay
|
if (__shouldSetNeedsDisplay(layer)) {
|
||||||
|| (flags.setOpaque && opaque != layer.opaque)
|
|
||||||
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) {
|
|
||||||
[layer setNeedsDisplay];
|
[layer setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,18 +667,7 @@ static UIColor *defaultTintColor = nil;
|
|||||||
if (flags.setOpaque)
|
if (flags.setOpaque)
|
||||||
ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired");
|
ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired");
|
||||||
|
|
||||||
if (flags.setFrame) {
|
ASPendingStateApplyMetricsToLayer(self, layer);
|
||||||
CGRect _bounds;
|
|
||||||
CGPoint _position;
|
|
||||||
ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position);
|
|
||||||
layer.bounds = _bounds;
|
|
||||||
layer.position = _position;
|
|
||||||
} else {
|
|
||||||
if (flags.setBounds)
|
|
||||||
layer.bounds = bounds;
|
|
||||||
if (flags.setPosition)
|
|
||||||
layer.position = position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly
|
- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly
|
||||||
@@ -668,9 +683,7 @@ static UIColor *defaultTintColor = nil;
|
|||||||
CALayer *layer = view.layer;
|
CALayer *layer = view.layer;
|
||||||
|
|
||||||
ASPendingStateFlags flags = _flags;
|
ASPendingStateFlags flags = _flags;
|
||||||
if (flags.needsDisplay
|
if (__shouldSetNeedsDisplay(layer)) {
|
||||||
|| (flags.setOpaque && opaque != view.opaque)
|
|
||||||
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) {
|
|
||||||
[view setNeedsDisplay];
|
[view setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,11 +696,6 @@ static UIColor *defaultTintColor = nil;
|
|||||||
if (flags.setZPosition)
|
if (flags.setZPosition)
|
||||||
layer.zPosition = zPosition;
|
layer.zPosition = zPosition;
|
||||||
|
|
||||||
// This should only be used for synchronous views wrapped by nodes.
|
|
||||||
if (flags.setFrame && !(flags.setBounds && flags.setPosition)) {
|
|
||||||
view.frame = frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags.setBounds)
|
if (flags.setBounds)
|
||||||
view.bounds = bounds;
|
view.bounds = bounds;
|
||||||
|
|
||||||
@@ -810,31 +818,16 @@ static UIColor *defaultTintColor = nil;
|
|||||||
if (flags.setAccessibilityIdentifier)
|
if (flags.setAccessibilityIdentifier)
|
||||||
view.accessibilityIdentifier = accessibilityIdentifier;
|
view.accessibilityIdentifier = accessibilityIdentifier;
|
||||||
|
|
||||||
// FIXME: How should we reconcile order-of-operations between setting frame, bounds, position?
|
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
||||||
// Note we can't read bounds and position in the background, so we have to keep the frame
|
if (flags.setFrame && setFrameDirectly) {
|
||||||
// value intact until application time (now).
|
// Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform
|
||||||
if (flags.setFrame) {
|
|
||||||
if (setFrameDirectly) {
|
|
||||||
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
|
||||||
|
|
||||||
// Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// 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.
|
// 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(layer.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.)");
|
ASDisplayNodeAssert(CATransform3DIsIdentity(layer.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
|
#endif
|
||||||
view.frame = frame;
|
view.frame = frame;
|
||||||
} else {
|
|
||||||
CGRect _bounds;
|
|
||||||
CGPoint _position;
|
|
||||||
ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position);
|
|
||||||
layer.bounds = _bounds;
|
|
||||||
layer.position = _position;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (flags.setBounds)
|
ASPendingStateApplyMetricsToLayer(self, layer);
|
||||||
layer.bounds = bounds;
|
|
||||||
if (flags.setPosition)
|
|
||||||
layer.position = position;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,13 +168,13 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) {
|
|||||||
{
|
{
|
||||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||||
ASDisplayNode *node = [ASDisplayNode new];
|
ASDisplayNode *node = [ASDisplayNode new];
|
||||||
XCTAssertFalse(node.pendingViewState.hasChanges);
|
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
|
||||||
[node view];
|
[node view];
|
||||||
XCTAssertEqual(node.alpha, 1);
|
XCTAssertEqual(node.alpha, 1);
|
||||||
node.alpha = 0;
|
node.alpha = 0;
|
||||||
XCTAssertEqual(node.view.alpha, 0);
|
XCTAssertEqual(node.view.alpha, 0);
|
||||||
XCTAssertEqual(node.alpha, 0);
|
XCTAssertEqual(node.alpha, 0);
|
||||||
XCTAssertFalse(node.pendingViewState.hasChanges);
|
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
|
||||||
XCTAssertFalse(ctrl.test_isFlushScheduled);
|
XCTAssertFalse(ctrl.test_isFlushScheduled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user