Introduced ASHierarchyState. Created ASDisplayNode+FrameworkPrivate.h. Fixed deadlock.

This commit is contained in:
Scott Goodson
2015-12-05 22:20:16 -08:00
parent 29897297c5
commit 840884272d
21 changed files with 281 additions and 153 deletions

View File

@@ -441,6 +441,8 @@
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; };
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
@@ -725,6 +727,7 @@
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = "<group>"; };
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1045,6 +1048,7 @@
058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */,
058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */,
058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */,
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */,
058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */,
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
@@ -1231,6 +1235,7 @@
257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */,
058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */,
0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */,
DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */,
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */,
257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */,
464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */,
@@ -1340,6 +1345,7 @@
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */,
509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */,
B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */,
DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */,
B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */,
B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */,
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */,

View File

@@ -25,7 +25,7 @@
if (!(self = [super init]))
return nil;
// use UITableViewCell defaults
// Use UITableViewCell defaults
_selectionStyle = UITableViewCellSelectionStyleDefault;
self.clipsToBounds = YES;

View File

@@ -6,19 +6,16 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASCollectionView.h"
#import "ASAssert.h"
#import "ASCollectionViewLayoutController.h"
#import "ASRangeController.h"
#import "ASCollectionDataController.h"
#import "ASBatchFetching.h"
#import "UICollectionViewLayout+ASConvenience.h"
#import "ASInternalHelpers.h"
#import "ASCollectionView.h"
#import "ASCollectionDataController.h"
#import "ASCollectionViewLayoutController.h"
#import "ASCollectionViewFlowLayoutInspector.h"
// FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASInternalHelpers.h"
#import "ASRangeController.h"
#import "UICollectionViewLayout+ASConvenience.h"
static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone;
static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
@@ -661,6 +658,8 @@ static BOOL _isInterceptedSelector(SEL sel)
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath
{
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
[node enterHierarchyState:ASHierarchyStateRangeManaged];
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
if (node.layoutDelegate == nil) {
node.layoutDelegate = self;

View File

@@ -412,10 +412,8 @@
*/
- (UIImage *)placeholderImage;
/** @name Description */
/**
* @abstract Return a description of the node
*
@@ -425,47 +423,5 @@
@end
@interface ASDisplayNode (ASDisplayNodePrivate)
/**
* This method has proven helpful in a few rare scenarios, similar to a category extension on UIView,
* but it's considered private API for now and its use should not be encouraged.
* @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode.
* If YES, this method must be called on the main thread and the node must not be layer-backed.
*/
- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy;
// The two methods below will eventually be exposed, but their names are subject to change.
/**
* @abstract Ensure that all rendering is complete for this node and its descendents.
*
* @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that
* placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController
* to implement their respective ".neverShowPlaceholders" option.
*
* If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately.
*
* This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an
* asynchronous display operation, and some already finished.
*
* In order to guarantee against deadlocks, this method should only be called on the main thread.
* It may block on the private queue, [_ASDisplayLayer displayQueue]
*/
- (void)recursivelyEnsureDisplay;
/**
* @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO.
*
* @discussion Nodes that are expensive to draw and expected to have placeholder even with
* .neverShowPlaceholders enabled should set this to YES.
*
* ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay.
*
* ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server,
* and are expected to support a placeholder state given that display is often blocked on slow data fetching.
*/
@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay;
@end
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")

View File

@@ -7,8 +7,9 @@
*/
#import "ASDisplayNode.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASLayoutOptionsPrivate.h"
#import <objc/runtime.h>
@@ -38,6 +39,9 @@
@end
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds)
#if TIME_DISPLAYNODE_OPS
#define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar)
@@ -323,17 +327,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
#pragma mark - Core
- (void)__tearDown:(BOOL)tearDown subnodesOfNode:(ASDisplayNode *)node
{
for (ASDisplayNode *subnode in node.subnodes) {
if (tearDown) {
[subnode __unloadNode];
} else {
[subnode __loadNode];
}
}
}
- (void)__unloadNode
{
ASDisplayNodeAssertThreadAffinity(self);
@@ -357,22 +350,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
[self layer];
}
- (ASDisplayNode *)__rasterizedContainerNode
{
ASDisplayNode *node = self.supernode;
while (node) {
if (node.shouldRasterizeDescendants) {
return node;
}
node = node.supernode;
}
return nil;
}
- (BOOL)__shouldLoadViewOrLayer
{
return ![self __rasterizedContainerNode];
return !(_hierarchyState & ASHierarchyStateRasterized);
}
- (BOOL)__shouldSize
@@ -645,30 +625,44 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
{
ASDisplayNodeAssertThreadAffinity(self);
ASDN::MutexLocker l(_propertyLock);
ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants),
@"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled");
return _flags.shouldRasterizeDescendants;
}
- (void)setShouldRasterizeDescendants:(BOOL)flag
- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize
{
ASDisplayNodeAssertThreadAffinity(self);
ASDN::MutexLocker l(_propertyLock);
if (_flags.shouldRasterizeDescendants == flag)
if (_flags.shouldRasterizeDescendants == shouldRasterize)
return;
_flags.shouldRasterizeDescendants = flag;
_flags.shouldRasterizeDescendants = shouldRasterize;
if (self.isNodeLoaded) {
//recursively tear down or build up subnodes
// Recursively tear down or build up subnodes.
// TODO: When disabling rasterization, preserve rasterized backing store as placeholderImage
// while the newly materialized subtree finishes rendering. Then destroy placeholderImage to save memory.
[self recursivelyClearContents];
[self __tearDown:flag subnodesOfNode:self];
if (flag == NO) {
[self _addSubnodeViewsAndLayers];
}
ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode *node) {
if (shouldRasterize) {
[node enterHierarchyState:ASHierarchyStateRasterized];
[node __unloadNode];
} else {
[node exitHierarchyState:ASHierarchyStateRasterized];
[node __loadNode];
}
});
if (self.interfaceState & ASInterfaceStateVisible) {
// TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip
// nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized.
[self recursivelyDisplayImmediately];
}
}
}
- (CGFloat)contentsScaleForDisplay
{
@@ -969,7 +963,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD
[_subnodes insertObject:subnode atIndex:subnodeIndex];
// Don't bother inserting the view/layer if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work.
if (!_flags.shouldRasterizeDescendants && ![self __rasterizedContainerNode]) {
if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) {
if (_layer) {
ASDisplayNodeCAssertMainThread();
@@ -1090,7 +1084,7 @@ static NSInteger incrementIfFound(NSInteger i) {
NSInteger aboveSublayerIndex = NSNotFound;
// Don't bother figuring out the sublayerIndex if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work.
if (!_flags.shouldRasterizeDescendants && ![self __rasterizedContainerNode]) {
if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) {
if (_layer) {
aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer];
ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace");
@@ -1345,7 +1339,15 @@ static NSInteger incrementIfFound(NSInteger i) {
- (void)__setSupernode:(ASDisplayNode *)supernode
{
ASDN::MutexLocker l(_propertyLock);
if (_supernode != supernode) {
ASHierarchyState oldHierarchyState = _supernode.hierarchyState;
_supernode = supernode;
if (_supernode) {
[self enterHierarchyState:_supernode.hierarchyState];
} else {
[self exitHierarchyState:oldHierarchyState];
}
}
}
// Track that a node will be displayed as part of the current node hierarchy.
@@ -1653,7 +1655,8 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
* @see https://github.com/facebook/AsyncDisplayKit/issues/900
* Possible solution is to push `isInCellNode` state downward on `addSubnode`/`removeFromSupernode`.
*/
- (BOOL)supportsInterfaceState {
- (BOOL)supportsInterfaceState
{
return ([self isKindOfClass:ASCellNode.class]
|| [self _supernodeWithClass:ASCellNode.class checkViewHierarchy:NO] != nil);
}
@@ -1664,23 +1667,23 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
return _interfaceState;
}
- (void)setInterfaceState:(ASInterfaceState)interfaceState
- (void)setInterfaceState:(ASInterfaceState)newState
{
ASInterfaceState oldValue;
ASInterfaceState oldState;
{
ASDN::MutexLocker l(_propertyLock);
oldValue = _interfaceState;
_interfaceState = interfaceState;
oldState = _interfaceState;
_interfaceState = newState;
}
if (interfaceState != oldValue) {
if ((interfaceState & ASInterfaceStateMeasureLayout) != (oldValue & ASInterfaceStateMeasureLayout)) {
if (newState != oldState) {
if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) {
// Trigger asynchronous measurement if it is not already cached or being calculated.
}
// Entered or exited data loading state.
if ((interfaceState & ASInterfaceStateFetchData) != (oldValue & ASInterfaceStateFetchData)) {
if (interfaceState & ASInterfaceStateFetchData) {
if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) {
if (newState & ASInterfaceStateFetchData) {
[self fetchData];
} else {
[self clearFetchedData];
@@ -1688,8 +1691,8 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
}
// Entered or exited contents rendering state.
if ((interfaceState & ASInterfaceStateDisplay) != (oldValue & ASInterfaceStateDisplay)) {
if (interfaceState & ASInterfaceStateDisplay) {
if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) {
if (newState & ASInterfaceStateDisplay) {
// Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here.
[self setDisplaySuspended:NO];
} else {
@@ -1699,14 +1702,13 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
}
// Entered or exited data loading state.
if ((interfaceState & ASInterfaceStateVisible) != (oldValue & ASInterfaceStateVisible)) {
if (interfaceState & ASInterfaceStateVisible) {
if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) {
if (newState & ASInterfaceStateVisible) {
// Consider providing a -didBecomeVisible.
} else {
// Consider providing a -didBecomeInvisible.
}
}
}
}
@@ -1724,6 +1726,40 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
});
}
- (ASHierarchyState)hierarchyState
{
ASDN::MutexLocker l(_propertyLock);
return _hierarchyState;
}
- (void)setHierarchyState:(ASHierarchyState)newState
{
ASHierarchyState oldState;
{
ASDN::MutexLocker l(_propertyLock);
oldState = _hierarchyState;
_hierarchyState = newState;
}
if (newState != oldState) {
LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState);
}
}
- (void)enterHierarchyState:(ASHierarchyState)hierarchyState
{
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.hierarchyState |= hierarchyState;
});
}
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState
{
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.hierarchyState &= (~hierarchyState);
});
}
- (void)layout
{
ASDisplayNodeAssertMainThread();

View File

@@ -37,6 +37,12 @@ extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node);
*/
extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node));
/**
Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the
node provided directly to the function call - only on all descendants.
*/
extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node));
/**
Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block.
*/

View File

@@ -7,8 +7,8 @@
*/
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer)
{
@@ -46,6 +46,13 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *
}
}
extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
{
for (ASDisplayNode *subnode in node.subnodes) {
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, block);
}
}
id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
{
CALayer *layer = node.layer;

View File

@@ -17,6 +17,7 @@
#import "ASAvailability.h"
#import "ASBaseDefines.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASLog.h"
#import "ASPhotosFrameworkImageRequest.h"
#import "ASEqualityHelpers.h"

View File

@@ -10,8 +10,9 @@
#import "ASBasicImageDownloader.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASThread.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASEqualityHelpers.h"
#import "ASThread.h"
@interface ASNetworkImageNode ()
{
@@ -30,10 +31,8 @@
BOOL _imageLoaded;
}
@end
@implementation ASNetworkImageNode
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader

View File

@@ -10,16 +10,14 @@
#import "ASTableViewInternal.h"
#import "ASAssert.h"
#import "ASBatchFetching.h"
#import "ASChangeSetDataController.h"
#import "ASCollectionViewLayoutController.h"
#import "ASLayoutController.h"
#import "ASRangeController.h"
#import "ASBatchFetching.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
// FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h"
#import "ASLayoutController.h"
#import "ASRangeController.h"
static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
@@ -829,6 +827,8 @@ static BOOL _isInterceptedSelector(SEL sel)
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath
{
ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath];
[node enterHierarchyState:ASHierarchyStateRangeManaged];
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
if (node.layoutDelegate == nil) {
node.layoutDelegate = self;

View File

@@ -9,9 +9,7 @@
#import "ASViewController.h"
#import "ASAssert.h"
#import "ASDimension.h"
// FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
@implementation ASViewController
{

View File

@@ -8,7 +8,7 @@
#import "ASRangeHandlerPreload.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
@implementation ASRangeHandlerPreload

View File

@@ -10,7 +10,7 @@
#import "ASDisplayNode.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
@interface ASRangeHandlerRender ()
@property (nonatomic,readonly) UIWindow *workingWindow;

View File

@@ -8,7 +8,7 @@
#import "ASRangeHandlerVisible.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
@implementation ASRangeHandlerVisible

View File

@@ -14,6 +14,7 @@
#import "ASAssert.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
@implementation _ASDisplayLayer
{

View File

@@ -15,6 +15,7 @@
#import "ASAssert.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Subclasses.h"
@interface _ASDisplayView ()

View File

@@ -10,6 +10,7 @@
#import "_ASAsyncTransaction.h"
#import "ASAssert.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
@implementation ASDisplayNode (AsyncDisplay)
@@ -84,7 +85,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
return;
}
BOOL rasterizingFromAscendent = [self __rasterizedContainerNode] != nil;
BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized);
// if super node is rasterizing descendents, subnodes will not have had layout calls becase they don't have layers
if (rasterizingFromAscendent) {
@@ -178,7 +179,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;
ASDisplayNodeAssert(rasterizing || ![self __rasterizedContainerNode], @"Rasterized descendants should never display unless being drawn into the rasterized container.");
ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), @"Rasterized descendants should never display unless being drawn into the rasterized container.");
if (!rasterizing && self.shouldRasterizeDescendants) {
CGRect bounds = self.bounds;
@@ -296,7 +297,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
ASDN::MutexLocker l(_propertyLock);
if ([self __rasterizedContainerNode]) {
if (_hierarchyState & ASHierarchyStateRasterized) {
return;
}

View File

@@ -0,0 +1,109 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
//
// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode.
// These methods must never be called or overridden by other classes.
//
#import "_ASDisplayLayer.h"
#import "_AS-objc-internal.h"
#import "ASDisplayNodeExtraIvars.h"
#import "ASDisplayNode.h"
#import "ASSentinel.h"
#import "ASThread.h"
#import "ASLayoutOptions.h"
/**
Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree.
Examples include rasterization and external driving of the .interfaceState property.
By passing this information explicitly, performance is optimized by avoiding iteration up the supernode chain.
Lastly, this avoidance of supernode traversal protects against the possibility of deadlocks when a supernode is
simultaneously attempting to materialize views / layers for its subtree (as many related methods require property locking)
Note: as the hierarchy deepens, more state properties may be enabled. However, state properties may never be disabled /
cancelled below the point they are enabled. They continue to the leaves of the hierarchy.
*/
typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
{
/** The node may or may not have a supernode, but no supernode has a special hierarchy-influencing option enabled. */
ASHierarchyStateNormal = 0,
/** The node has a supernode with .shouldRasterizeDescendants = YES.
Note: the root node of the rasterized subtree (the one with the property set on it) will NOT have this state set. */
ASHierarchyStateRasterized = 1 << 0,
/** The node or one of its supernodes is managed by a class like ASRangeController. Most commonly, these nodes are
ASCellNode objects or a subnode of one, and are used in ASTableView or ASCollectionView.
These nodes also recieve regular updates to the .interfaceState property with more detailed status information. */
ASHierarchyStateRangeManaged = 1 << 1,
};
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
{
@protected
ASInterfaceState _interfaceState;
ASHierarchyState _hierarchyState;
}
// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements.
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
- (void)exitInterfaceState:(ASInterfaceState)interfaceState;
// These methods are recursive, and either union or remove the provided hierarchyState to all sub-elements.
- (void)enterHierarchyState:(ASHierarchyState)hierarchyState;
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState;
/**
* @abstract Returns the Hierarchy State of the node.
*
* @return The current ASHierarchyState of the node, indicating whether it is rasterized or managed by a range controller.
*
* @see ASInterfaceState
*/
@property (nonatomic, readwrite) ASHierarchyState hierarchyState;
// The two methods below will eventually be exposed, but their names are subject to change.
/**
* @abstract Ensure that all rendering is complete for this node and its descendents.
*
* @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that
* placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController
* to implement their respective ".neverShowPlaceholders" option.
*
* If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately.
*
* This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an
* asynchronous display operation, and some already finished.
*
* In order to guarantee against deadlocks, this method should only be called on the main thread.
* It may block on the private queue, [_ASDisplayLayer displayQueue]
*/
- (void)recursivelyEnsureDisplay;
/**
* @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO.
*
* @discussion Nodes that are expensive to draw and expected to have placeholder even with
* .neverShowPlaceholders enabled should set this to YES.
*
* ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay.
*
* ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server,
* and are expected to support a placeholder state given that display is often blocked on slow data fetching.
*/
@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay;
@end
@interface UIView (ASDisplayNodeInternal)
@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node;
@end
@interface CALayer (ASDisplayNodeInternal)
@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node;
@end

View File

@@ -8,9 +8,11 @@
#import "_ASCoreAnimationExtras.h"
#import "_ASPendingState.h"
#import "ASInternalHelpers.h"
#import "ASAssert.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASEqualityHelpers.h"
/**
@@ -219,9 +221,22 @@
- (void)setNeedsDisplay
{
ASDisplayNode *rasterizedContainerNode = [self __rasterizedContainerNode];
if (rasterizedContainerNode) {
if (_hierarchyState & ASHierarchyStateRasterized) {
ASPerformBlockOnMainThread(^{
// The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node
// begins materializing the view / layer heirarchy (locking itself or a descendant) while this node walks up
// the tree and requires locking that node to access .shouldRasterizeDescendants.
// For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized.
ASDisplayNodeAssertMainThread();
ASDisplayNode *rasterizedContainerNode = self.supernode;
while (rasterizedContainerNode) {
if (rasterizedContainerNode.shouldRasterizeDescendants) {
break;
}
rasterizedContainerNode = rasterizedContainerNode.supernode;
}
[rasterizedContainerNode setNeedsDisplay];
});
} else {
[_layer setNeedsDisplay];
}

View File

@@ -22,7 +22,8 @@
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)());
typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
{
ASDisplayNodeMethodOverrideNone = 0,
ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0,
ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1,
@@ -73,8 +74,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
_ASPendingState *_pendingViewState;
ASInterfaceState _interfaceState;
struct ASDisplayNodeFlags {
// public properties
unsigned synchronous:1;
@@ -118,9 +117,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
// Bitmask to check which methods an object overrides.
@property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides;
// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements.
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
- (void)exitInterfaceState:(ASInterfaceState)interfaceState;
// Swizzle to extend the builtin functionality with custom logic
- (BOOL)__shouldLoadViewOrLayer;
@@ -149,9 +145,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
// Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated.
- (void)displayImmediately;
// Returns the ancestor node that rasterizes descendants, or nil if none.
- (ASDisplayNode *)__rasterizedContainerNode;
// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses.
- (id)initWithViewClass:(Class)viewClass;
@@ -160,12 +153,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
@property (nonatomic, assign) CGFloat contentsScaleForDisplay;
@end
/**
* This method has proven helpful in a few rare scenarios, similar to a category extension on UIView,
* but it's considered private API for now and its use should not be encouraged.
* @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode.
* If YES, this method must be called on the main thread and the node must not be layer-backed.
*/
- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy;
@interface UIView (ASDisplayNodeInternal)
@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node;
@end
@interface CALayer (ASDisplayNodeInternal)
@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node;
@end

View File

@@ -68,7 +68,7 @@
{
// If it's a space character and we have custom word kerning, use the whitespace action control character.
if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ')
return NSControlCharacterWhitespaceAction;
return NSControlCharacterActionWhitespace;
return defaultAction;
}