diff --git a/CHANGELOG.md b/CHANGELOG.md index e33d75fe..ab9b7a3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 347d41c4..8e34678a 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -24,6 +24,7 @@ #import #import +#import #import #import @@ -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> 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 { diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 2cf772ae..b555b031 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -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)) { diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 3c7c2e88..d44ba769 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -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 diff --git a/Tests/ASDisplayLayerTests.m b/Tests/ASDisplayLayerTests.m index 5f20e928..a9f08757 100644 --- a/Tests/ASDisplayLayerTests.m +++ b/Tests/ASDisplayLayerTests.m @@ -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 {