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:
appleguy
2016-02-22 20:41:59 -08:00
8 changed files with 205 additions and 184 deletions

View File

@@ -79,6 +79,17 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL 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.
*
@@ -257,7 +268,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_contentsScaleForDisplay = ASScreenScale();
_displaySentinel = [[ASSentinel alloc] init];
_preferredFrameSize = CGSizeZero;
_pendingViewState = [_ASPendingState new];
}
- (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
{
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
@@ -1894,6 +1899,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (ASSizeRange)constrainedSizeForCalculatedLayout
{
ASDN::MutexLocker l(_propertyLock);
return _constrainedSize;
}

View File

@@ -123,6 +123,7 @@
if (!ASObjectIsEqual(_image, image)) {
_image = image;
ASDN::MutexUnlocker u(_imageLock);
[self invalidateCalculatedLayout];
[self setNeedsDisplay];
}

View File

@@ -37,7 +37,7 @@
#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
#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.
/// This function must be called with the node's lock already held (after _bridge_prologue_write).
ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) {
BOOL loaded = __loaded(node);
if (ASDisplayNodeThreadIsMain()) {
return node.nodeLoaded;
return loaded;
} else {
if (node.nodeLoaded && !node->_pendingViewState.hasChanges) {
[ASPendingStateController.sharedInstance registerNode:node];
if (loaded && !node->_pendingViewState.hasChanges) {
[[ASPendingStateController sharedInstance] registerNode:node];
}
return NO;
}
};
#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded ? \
#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded(self) ? \
(_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\
: self.pendingViewState.viewAndPendingViewStateProperty
: ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty
#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); \
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); \
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 _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,
* 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
{
_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;
self.position = position;
} else if (nodeLoaded && !isMainThread) {
if (!_pendingViewState.hasChanges) {
[ASPendingStateController.sharedInstance registerNode:self];
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
struct ASDisplayNodeFlags flags = _flags;
BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked;
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];
});
} else {
if (self.nodeLoaded) {
if (ASDisplayNodeThreadIsMain()) {
_messageToViewOrLayer(setNeedsDisplay);
} else {
if (!_pendingViewState.hasChanges) {
[ASPendingStateController.sharedInstance registerNode:self];
}
[self __setNeedsDisplay];
[_pendingViewState setNeedsDisplay];
}
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
if (shouldApply) {
// If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a
// message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay,
// which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared.
_messageToViewOrLayer(setNeedsDisplay);
} else {
[self __setNeedsDisplay];
[ASDisplayNodeGetPendingState(self) setNeedsDisplay];
}
[self __setNeedsDisplay];
}
}
- (void)setNeedsLayout
{
_bridge_prologue_write;
if (self.nodeLoaded) {
if (ASDisplayNodeThreadIsMain()) {
[self __setNeedsLayout];
_messageToViewOrLayer(setNeedsLayout);
} else {
if (!_pendingViewState.hasChanges) {
[ASPendingStateController.sharedInstance registerNode:self];
}
// NOTE: We will call [self __setNeedsLayout] just before we apply
// the pending state. We need to call it on main if the node is loaded
// to support implicit hierarchy management.
[_pendingViewState setNeedsLayout];
}
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
if (shouldApply) {
// The node is loaded and we're on main.
// Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging
// the view or layer to ensure that measurement and implicitly added subnodes have been handled.
[self __setNeedsLayout];
_messageToViewOrLayer(setNeedsLayout);
} else if (__loaded(self)) {
// The node is loaded but we're not on main.
// We will call [self __setNeedsLayout] when we apply
// the pending state. We need to call it on main if the node is loaded
// to support implicit hierarchy management.
[ASDisplayNodeGetPendingState(self) setNeedsLayout];
} else {
// The node is not loaded and we're not on main.
[self __setNeedsLayout];
}
}
@@ -504,28 +528,29 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie
- (UIViewContentMode)contentMode
{
_bridge_prologue_read;
if (__loaded) {
if (__loaded(self)) {
if (_flags.layerBacked) {
return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity);
} else {
return _view.contentMode;
}
} else {
return self.pendingViewState.contentMode;
return ASDisplayNodeGetPendingState(self).contentMode;
}
}
- (void)setContentMode:(UIViewContentMode)contentMode
{
_bridge_prologue_write;
if (__loaded) {
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
if (shouldApply) {
if (_flags.layerBacked) {
_layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode);
} else {
_view.contentMode = contentMode;
}
} else {
self.pendingViewState.contentMode = contentMode;
ASDisplayNodeGetPendingState(self).contentMode = contentMode;
}
}

View File

@@ -23,9 +23,13 @@
@protocol _ASDisplayLayerDelegate;
@class _ASDisplayLayer;
@class _ASPendingState;
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)
{
ASDisplayNodeMethodOverrideNone = 0,
@@ -36,7 +40,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4
};
@class _ASPendingState;
@class _ASDisplayNodePosition;
FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification;
@@ -52,10 +55,37 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
@package
_ASPendingState *_pendingViewState;
@protected
// Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads.
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;
ASSentinel *_displaySentinel;
@@ -86,39 +116,12 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
ASDisplayNodeDidLoadBlock _nodeLoadedBlock;
Class _viewClass;
Class _layerClass;
UIView *_view;
CALayer *_layer;
UIImage *_placeholderImage;
CALayer *_placeholderLayer;
// keeps track of nodes/subnodes that have not finished display, used with placeholders
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;
@@ -139,9 +142,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
// The _ASDisplayLayer backing the node, if any.
@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.
@property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides;

View File

@@ -51,9 +51,9 @@ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation,
ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position)
{
*bounds = (CGRect){ origin, rect.size };
*position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
rect.origin.y + rect.size.height * anchorPoint.y);
*bounds = (CGRect){ origin, rect.size };
*position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
rect.origin.y + rect.size.height * anchorPoint.y);
}
@interface NSIndexPath (ASInverseComparison)

View File

@@ -32,7 +32,7 @@
{
self = [super init];
if (self) {
_dirtyNodes = [ASWeakSet new];
_dirtyNodes = [[ASWeakSet alloc] init];
}
return self;
}
@@ -40,21 +40,15 @@
+ (ASPendingStateController *)sharedInstance
{
static dispatch_once_t onceToken;
static ASPendingStateController *controller;
static ASPendingStateController *controller = nil;
dispatch_once(&onceToken, ^{
controller = [ASPendingStateController new];
controller = [[ASPendingStateController alloc] init];
});
return controller;
}
#pragma mark External API
- (void)flush
{
ASDisplayNodeAssertMainThread();
[self flushNow];
}
- (void)registerNode:(ASDisplayNode *)node
{
ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node);
@@ -64,6 +58,25 @@
[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
/**
@@ -77,27 +90,10 @@
_flags.pendingFlush = YES;
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
@implementation ASPendingStateController (Testing)

View File

@@ -14,6 +14,10 @@
#import "ASInternalHelpers.h"
#import "ASDisplayNodeInternal.h"
#define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \
|| (flags.setOpaque && opaque != (layer).opaque)\
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, (layer).backgroundColor)))
typedef struct {
// Properties
int needsDisplay:1;
@@ -106,6 +110,30 @@ typedef struct {
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 opaque=opaque;
@@ -560,9 +588,7 @@ static UIColor *defaultTintColor = nil;
{
ASPendingStateFlags flags = _flags;
if (flags.needsDisplay
|| (flags.setOpaque && opaque != layer.opaque)
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) {
if (__shouldSetNeedsDisplay(layer)) {
[layer setNeedsDisplay];
}
@@ -641,18 +667,7 @@ static UIColor *defaultTintColor = nil;
if (flags.setOpaque)
ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired");
if (flags.setFrame) {
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;
}
ASPendingStateApplyMetricsToLayer(self, layer);
}
- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly
@@ -668,9 +683,7 @@ static UIColor *defaultTintColor = nil;
CALayer *layer = view.layer;
ASPendingStateFlags flags = _flags;
if (flags.needsDisplay
|| (flags.setOpaque && opaque != view.opaque)
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) {
if (__shouldSetNeedsDisplay(layer)) {
[view setNeedsDisplay];
}
@@ -683,11 +696,6 @@ static UIColor *defaultTintColor = nil;
if (flags.setZPosition)
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)
view.bounds = bounds;
@@ -810,31 +818,16 @@ static UIColor *defaultTintColor = nil;
if (flags.setAccessibilityIdentifier)
view.accessibilityIdentifier = accessibilityIdentifier;
// 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).
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
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
if (flags.setFrame && setFrameDirectly) {
// 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(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.)");
// 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.)");
#endif
view.frame = frame;
} else {
CGRect _bounds;
CGPoint _position;
ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position);
layer.bounds = _bounds;
layer.position = _position;
}
view.frame = frame;
} else {
if (flags.setBounds)
layer.bounds = bounds;
if (flags.setPosition)
layer.position = position;
ASPendingStateApplyMetricsToLayer(self, layer);
}
}

View File

@@ -168,13 +168,13 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) {
{
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
ASDisplayNode *node = [ASDisplayNode new];
XCTAssertFalse(node.pendingViewState.hasChanges);
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
[node view];
XCTAssertEqual(node.alpha, 1);
node.alpha = 0;
XCTAssertEqual(node.view.alpha, 0);
XCTAssertEqual(node.alpha, 0);
XCTAssertFalse(node.pendingViewState.hasChanges);
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
XCTAssertFalse(ctrl.test_isFlushScheduled);
}