Eliminate +[ASDisplayNode initialize] in production, reduce work in debug

This commit is contained in:
Adlai Holler
2018-07-23 20:28:49 -07:00
parent d9d5b12475
commit d35bb12ea0
5 changed files with 41 additions and 81 deletions
+1
View File
@@ -25,6 +25,7 @@
- Reduced binary size by disabling exception support (which we don't use.) [Adlai Holler](https://github.com/Adlai-Holler)
- Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029)
- Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042)
- Eliminate `+[ASDisplayNode initialize]` in production, and drastically reduce its work when debugging. [Adlai Holler](https://github.com/Adlai-Holler)
## 2.7
+30 -64
View File
@@ -24,6 +24,7 @@
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <objc/runtime.h>
#import <unordered_map>
#import <AsyncDisplayKit/_ASAsyncTransaction.h>
#import <AsyncDisplayKit/_ASAsyncTransactionContainer+Private.h>
@@ -111,51 +112,37 @@ _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node)
}
/**
* Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL.
* Fetch the ASDisplayNodeFlags and ASDisplayNodeMethodOverrides for the given class.
*
* @param c the class, required
* @param instance the instance, which may be nil. (If so, the class is inspected instead)
* @remarks The instance value is used only if we suspect the class may be dynamic (because it overloads
* +respondsToSelector: or -respondsToSelector.) In that case we use our "slow path", calling this
* method on each -init and passing the instance value. While this may seem like an unlikely scenario,
* it turns our our own internal tests use a dynamic class, so it's worth capturing this edge case.
*
* @return ASDisplayNode flags.
* @param c the class, required
* @param flags a ref to a flags struct that we will initialize
* @param overrides a ref to an overrides field that we will initialize
*/
static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *instance)
static void GetASDisplayNodeFlags(Class c, struct ASDisplayNodeFlags &flags, ASDisplayNodeMethodOverrides &overrides)
{
ASDisplayNodeCAssertNotNil(c, @"class is required");
static ASDN::StaticMutex lock;
static std::unordered_map<Class, std::pair<struct ASDisplayNodeFlags, ASDisplayNodeMethodOverrides>> map;
struct ASDisplayNodeFlags flags = {0};
ASDN::StaticMutexLocker locker(lock);
auto it = map.find(c);
if (it != map.cend()) {
auto pair = it->second;
flags = pair.first;
overrides = pair.second;
return;
}
flags = {0};
flags.isInHierarchy = NO;
flags.displaysAsynchronously = YES;
flags.shouldAnimateSizeChanges = YES;
flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
if (instance) {
flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
} else {
flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
}
return flags;
}
/**
* Returns ASDisplayNodeMethodOverrides for the given class
*
* @param c the class, required.
*
* @return ASDisplayNodeMethodOverrides.
*/
static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
{
ASDisplayNodeCAssertNotNil(c, @"class is required");
ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone;
overrides = ASDisplayNodeMethodOverrideNone;
// Handling touches
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesBegan:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesBegan;
@@ -201,12 +188,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
overrides |= ASDisplayNodeMethodOverrideCalcSizeThatFits;
}
return overrides;
map.insert({c, {flags, overrides}});
}
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
+ (void)initialize
{
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
if (self != [ASDisplayNode class]) {
// Subclasses should never override these. Use unused to prevent warnings
@@ -222,7 +209,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
// Check if subnodes where modified during the creation of the layout
__block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_locked_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) {
NSArray *oldSubnodes = _self.subnodes;
ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_locked_layoutElementThatFits:), sizeRange);
ASLayoutSpec *layoutElement = ((ASLayoutSpec *(*)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_locked_layoutElementThatFits:), sizeRange);
NSArray *subnodes = _self.subnodes;
ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior.");
for (NSInteger i = 0; i < oldSubnodes.count; i++) {
@@ -231,29 +218,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return layoutElement;
});
}
#endif
// Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values
// when each instance is constructed. These values don't change for each class, so there is significant performance benefit
// in doing it here. +initialize is guaranteed to be called before any instance method so it is safe to add this method here.
// Note that we take care to detect if the class overrides +respondsToSelector: or -respondsToSelector and take the slow path
// (recalculating for each instance) to make sure we are always correct.
BOOL classOverridesRespondsToSelector = ASSubclassOverridesClassSelector([NSObject class], self, @selector(respondsToSelector:));
BOOL instancesOverrideRespondsToSelector = ASSubclassOverridesSelector([NSObject class], self, @selector(respondsToSelector:));
struct ASDisplayNodeFlags flags = GetASDisplayNodeFlags(self, nil);
ASDisplayNodeMethodOverrides methodOverrides = GetASDisplayNodeMethodOverrides(self);
__unused Class initializeSelf = self;
IMP staticInitialize = imp_implementationWithBlock(^(ASDisplayNode *node) {
ASDisplayNodeAssert(node.class == initializeSelf, @"Node class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", node.class, initializeSelf);
node->_flags = (classOverridesRespondsToSelector || instancesOverrideRespondsToSelector) ? GetASDisplayNodeFlags(node.class, node) : flags;
node->_methodOverrides = (classOverridesRespondsToSelector) ? GetASDisplayNodeMethodOverrides(node.class) : methodOverrides;
});
class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@");
}
#endif
#if !AS_INITIALIZE_FRAMEWORK_MANUALLY
+ (void)load
@@ -274,14 +240,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
#pragma mark - Lifecycle
- (void)_staticInitialize
{
ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden");
}
- (void)_initializeInstance
{
[self _staticInitialize];
GetASDisplayNodeFlags(self.class, _flags, _methodOverrides);
#if ASEVENTLOG_ENABLE
_eventLog = [[ASEventLog alloc] initWithObject:self];
@@ -1868,6 +1829,11 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
[self displayDidFinish];
}
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
return nil;
}
- (void)displayWillStart {}
- (void)displayWillStartAsynchronously:(BOOL)asynchronously
{
+3 -14
View File
@@ -43,19 +43,6 @@
return nil; \
} \
- (NSObject *)drawParameters
{
__instanceLock__.lock();
BOOL implementsDrawParameters = _flags.implementsDrawParameters;
__instanceLock__.unlock();
if (implementsDrawParameters) {
return [self drawParametersForAsyncLayer:self.asyncLayer];
} else {
return nil;
}
}
- (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock displayBlocks:(NSMutableArray *)displayBlocks
{
// Skip subtrees that are hidden or zero alpha.
@@ -191,11 +178,13 @@
CGColorRef borderColor = self.borderColor;
CGFloat borderWidth = self.borderWidth;
CGFloat contentsScaleForDisplay = _contentsScaleForDisplay;
auto asyncLayer = [self _locked_asyncLayer];
ASDisplayNodeAssertNotNil(asyncLayer, @"Expected async layer to be non-nil since we're doing asynchronous drawing.");
__instanceLock__.unlock();
// Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing.
id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil);
id drawParameters = (shouldBeginRasterizing == NO ? [self drawParametersForAsyncLayer:asyncLayer] : nil);
// Only the -display methods should be called if we can't size the graphics buffer to use.
if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) {
+2 -1
View File
@@ -121,7 +121,6 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest
unsigned implementsDrawRect:1;
unsigned implementsImageDisplay:1;
unsigned implementsDrawParameters:1;
// internal state
unsigned isEnteringHierarchy:1;
@@ -367,6 +366,8 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest
- (BOOL)_locked_insetsLayoutMarginsFromSafeArea;
- (_ASDisplayLayer *)_locked_asyncLayer;
@end
NS_ASSUME_NONNULL_END
+5 -2
View File
@@ -183,6 +183,11 @@ static _ASDisplayLayerTestDelegateClassModes _class_modes;
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
// If the mode isn't set, pretend we don't implement this method.
if (!(_modes & _ASDisplayLayerTestDelegateModeDrawParameters)) {
return [super drawParametersForAsyncLayer:layer];
}
_drawParametersCount++;
return self;
}
@@ -196,8 +201,6 @@ static _ASDisplayLayerTestDelegateClassModes _class_modes;
{
if (sel_isEqual(selector, @selector(didDisplayAsyncLayer:))) {
return (_modes & _ASDisplayLayerTestDelegateModeDidDisplay);
} else if (sel_isEqual(selector, @selector(drawParametersForAsyncLayer:))) {
return (_modes & _ASDisplayLayerTestDelegateModeDrawParameters);
} else if (sel_isEqual(selector, @selector(willDisplayAsyncLayer:))) {
return (_modes & _ASDisplayLayerTestDelegateModeWillDisplay);
} else {