ComponentKit/Core/ComponentTree/CKTreeNode.mm (314 lines of code) (raw):

/* * 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. * */ #import "CKTreeNode.h" #import <ComponentKit/CKComponent.h> #import <ComponentKit/CKComponentInternal.h> #import <ComponentKit/CKComponentSubclass.h> #import <ComponentKit/CKInternalHelpers.h> #import <ComponentKit/CKRenderComponentProtocol.h> #import <ComponentKit/CKRootTreeNode.h> #import <ComponentKit/CKMutex.h> #include <tuple> #include <atomic> #import "CKThreadLocalComponentScope.h" #import "CKRenderHelpers.h" namespace CK { namespace TreeNode { CKTreeNode *nodeForComponent(id<CKComponentProtocol> component) { CKThreadLocalComponentScope *currentScope = CKThreadLocalComponentScope::currentScope(); if (currentScope == nullptr) { return nil; } // `nodeForComponent` is being called for every non-render component from the base constructor of `CKComponent`. // We can rely on this infomration to increase the `componentAllocations` counter. currentScope->componentAllocations++; CKTreeNode *node = currentScope->stack.top().node; if ([node.scopeHandle acquireFromComponent:component]) { return node; } RCCAssertWithCategory([component.class controllerClass] == nil || CKSubclassOverridesInstanceMethod([CKComponent class], component.class, @selector(buildController)) || [component conformsToProtocol:@protocol(CKRenderComponentProtocol)], NSStringFromClass([component class]), @"Component has a controller but no scope! Make sure you construct your scope(self) " "before constructing the component or CKComponentTestRootScope at the start of the test."); return nil; } } } @interface CKTreeNode () @property (nonatomic, weak, readwrite) id<CKComponentProtocol> component; @property (nonatomic, strong, readwrite) CKComponentScopeHandle *scopeHandle; @property (nonatomic, assign, readwrite) CKTreeNodeIdentifier nodeIdentifier; @end @implementation CKTreeNode // Base initializer - (instancetype)initWithPreviousNode:(CKTreeNode *)previousNode scopeHandle:(CKComponentScopeHandle *)scopeHandle { static std::atomic_int32_t nextGlobalIdentifier; if (self = [super init]) { _scopeHandle = scopeHandle; _nodeIdentifier = previousNode ? previousNode.nodeIdentifier : ++nextGlobalIdentifier; _scopeHandle.treeNode = self; } return self; } // Render initializer - (instancetype)initWithComponent:(id<CKRenderComponentProtocol>)component parent:(CKTreeNode *)parent previousNode:(CKTreeNode *)previousNode scopeRoot:(CKComponentScopeRoot *)scopeRoot componentKey:(const CKTreeNodeComponentKey&)componentKey stateUpdates:(const CKComponentStateUpdateMap &)stateUpdates { CKComponentScopeHandle *scopeHandle = CKRender::ScopeHandle::Render::create(component, previousNode, scopeRoot, stateUpdates); if (self = [self initWithPreviousNode:previousNode scopeHandle:scopeHandle]) { [self linkComponent:component withKey:componentKey toParent:parent inScopeRoot:scopeRoot]; // Update the treeNode on the component [component acquireTreeNode:self]; // Finalize the node/scope registration. [scopeHandle forceAcquireFromComponent:component]; [scopeHandle resolveAndRegisterInScopeRoot:scopeRoot]; } return self; } // Scope init - (instancetype)initWithOwner:(CKTreeNode *)owner previousNode:(CKTreeNode *)previousNode scopeRoot:(CKComponentScopeRoot *)scopeRoot componentKey:(const CKTreeNodeComponentKey&)componentKey initialStateCreator:(id (^)(void))initialStateCreator stateUpdates:(const CKComponentStateUpdateMap &)stateUpdates requiresScopeHandle:(BOOL)requiresScopeHandle { RCAssertNotNil(owner, @"Must have an owner"); CKComponentScopeHandle *scopeHandle = _createScopeHandle(scopeRoot, previousNode, componentKey.componentTypeName, initialStateCreator, stateUpdates, requiresScopeHandle); if (self = [self initWithPreviousNode:previousNode scopeHandle:scopeHandle]) { _componentKey = componentKey; [owner setChild:self forComponentKey:componentKey]; } return self; } + (instancetype)rootNode { return [super new]; } - (void)linkComponent:(id<CKComponentProtocol>)component withKey:(const CKTreeNodeComponentKey&)componentKey toParent:(CKTreeNode *)parent inScopeRoot:(CKComponentScopeRoot *)scopeRoot { _component = component; _componentKey = componentKey; [parent setChild:self forComponentKey:componentKey]; scopeRoot.rootNode.registerNode(self, parent); } - (void)linkComponent:(id<CKComponentProtocol>)component toParent:(CKTreeNode *)parent inScopeRoot:(CKComponentScopeRoot *)scopeRoot { // The existing `_componentKey` that was created by the scope, is an owner based key; // hence, we extract the `unique identifer` and the `keys` vector from it and recreate a parent based key based on this information. auto const componentKey = [parent createKeyForComponentTypeName:component.typeName identifier:_componentKey.identifier keys:_componentKey.keys type:CKTreeNodeComponentKey::Type::parent]; [self linkComponent:component withKey:componentKey toParent:parent inScopeRoot:scopeRoot]; } - (id)state { return _scopeHandle.state; } - (const CKTreeNodeComponentKey &)componentKey { return _componentKey; } - (void)reusePreviousNode:(CKTreeNode *)node inScopeRoot:(CKComponentScopeRoot *)scopeRoot { // Transfer the children vector from the reused node. _children = node->_children; for (auto const &child : _children) { if (child.key.type() == CKTreeNodeComponentKey::Type::parent) { [child.node didReuseWithParent:self inScopeRoot:scopeRoot]; } } } - (void)didReuseWithParent:(CKTreeNode *)parent inScopeRoot:(CKComponentScopeRoot *)scopeRoot { // In case that CKComponentScope was created, but not acquired from the component (for example: early nil return) , // the component was never linked to the scope handle/tree node, hence, we should stop the recursion here. if (self.component == nil) { return; } RCAssert(parent != nil, @"The parent cannot be nil; every node should have a valid parent."); scopeRoot.rootNode.registerNode(self, parent); if (_scopeHandle) { // Register the reused comopnent in the new scope root. [scopeRoot registerComponent:_component]; auto const controller = _scopeHandle.controller; if (controller) { // Register the controller in the new scope root. [scopeRoot registerComponentController:controller]; } } for (auto const &child : _children) { if (child.key.type() == CKTreeNodeComponentKey::Type::parent) { [child.node didReuseWithParent:self inScopeRoot:scopeRoot]; } } } - (std::vector<CKTreeNode *>)children { std::vector<CKTreeNode *> children; for (auto const &child : _children) { if (child.key.type() == CKTreeNodeComponentKey::Type::parent) { children.push_back(child.node); } } return children; } - (size_t)childrenSize { return _children.size(); } - (CKTreeNode *)childForComponentKey:(const CKTreeNodeComponentKey &)key { for (auto const &child : _children) { if (child.key == key) { return child.node; } } return nil; } - (CKTreeNodeComponentKey)createKeyForComponentTypeName:(const char *)componentTypeName identifier:(id<NSObject>)identifier keys:(const std::vector<id<NSObject>> &)keys type:(CKTreeNodeComponentKey::Type)type { NSUInteger keyCounter = CKTreeNodeComponentKey::startOffsetForType(type); for (auto const &child : _children) { if (child.key.componentTypeName == componentTypeName && RCObjectIsEqual(child.key.identifier, identifier)) { keyCounter += 2; } } return CKTreeNodeComponentKey{componentTypeName, keyCounter, identifier, keys}; } - (void)setChild:(CKTreeNode *)child forComponentKey:(const CKTreeNodeComponentKey &)componentKey { _children.push_back(CKTreeNodeComponentKeyToNode{.key = componentKey, .node = child}); } static CKComponentScopeHandle *_createScopeHandle(CKComponentScopeRoot *scopeRoot, CKTreeNode *previousNode, const char *componentTypeName, id (^initialStateCreator)(void), const CKComponentStateUpdateMap &stateUpdates, BOOL requiresScopeHandle) { RCCAssertNotNil(initialStateCreator, @"Must have an initial state creator"); if (requiresScopeHandle == NO) { RCCAssertNil(previousNode.scopeHandle, @"requiresScopeHandle is false but previous node has scope handle"); return nil; } if (previousNode != nil) { RCCAssertNotNil(previousNode.scopeHandle, @"requiresScopeHandle is true but no scopeHandle on previous node"); return [previousNode.scopeHandle newHandleWithStateUpdates:stateUpdates]; } else { return [[CKComponentScopeHandle alloc] initWithListener:scopeRoot.listener rootIdentifier:scopeRoot.globalIdentifier componentTypeName:componentTypeName initialState:(initialStateCreator ? initialStateCreator() : nil)]; } } + (CKComponentScopePair)childPairForPair:(const CKComponentScopePair &)pair newRoot:(CKComponentScopeRoot *)newRoot componentTypeName:(const char *)componentTypeName identifier:(id)identifier keys:(const std::vector<id<NSObject>> &)keys initialStateCreator:(id (^)(void))initialStateCreator stateUpdates:(const CKComponentStateUpdateMap &)stateUpdates requiresScopeHandle:(BOOL)requiresScopeHandle { CKTreeNodeComponentKey componentKey = [pair.node createKeyForComponentTypeName:componentTypeName identifier:identifier keys:keys type:CKTreeNodeComponentKey::Type::owner]; const auto previousNode = [pair.previousNode childForComponentKey:componentKey]; const auto node = [[CKTreeNode alloc] initWithOwner:pair.node previousNode:previousNode scopeRoot:newRoot componentKey:componentKey initialStateCreator:initialStateCreator stateUpdates:stateUpdates requiresScopeHandle:requiresScopeHandle]; return CKComponentScopePair{.node = node, .previousNode = previousNode}; } + (CKComponentScopePair)childPairForComponent:(id<CKRenderComponentProtocol>)component parent:(CKTreeNode *)parent previousParent:(CKTreeNode *)previousParent scopeRoot:(CKComponentScopeRoot *)scopeRoot stateUpdates:(const CKComponentStateUpdateMap &)stateUpdates { auto const componentKey = [parent createKeyForComponentTypeName:component.typeName identifier:component.componentIdentifier keys:{} type:CKTreeNodeComponentKey::Type::parent]; auto const previousNode = [previousParent childForComponentKey:componentKey]; const auto node = [[CKTreeNode alloc] initWithComponent:component parent:parent previousNode:previousNode scopeRoot:scopeRoot componentKey:componentKey stateUpdates:stateUpdates]; return CKComponentScopePair{.node = node, .previousNode = previousNode}; } #pragma mark - Helpers #if DEBUG // Iterate threw the nodes according to the **parent** based key - (NSArray<NSString *> *)debugDescriptionNodes { NSString *const selfDescription = [NSString stringWithFormat:@"- %s %d - %@", _component.typeName, _nodeIdentifier, self]; NSMutableArray<NSString *> *debugDescriptionNodes = [NSMutableArray arrayWithArray:@[selfDescription]]; for (auto const &child : _children) { if (child.key.type() == CKTreeNodeComponentKey::Type::parent) { for (NSString *s in [child.node debugDescriptionNodes]) { [debugDescriptionNodes addObject:[@" " stringByAppendingString:s]]; } } } return debugDescriptionNodes; } // Iterate threw the nodes according to the **owner** based key - (NSArray<NSString *> *)debugDescriptionComponents { NSMutableArray<NSString *> *childrenDebugDescriptions = [NSMutableArray new]; for (auto const &child : _children) { if (child.key.type() == CKTreeNodeComponentKey::Type::owner) { auto const description = [NSString stringWithFormat:@"- %s%@%@", child.key.componentTypeName, (child.key.identifier ? [NSString stringWithFormat:@":%@", child.key.identifier] : @""), child.key.keys.empty() ? @"" : formatKeys(child.key.keys)]; [childrenDebugDescriptions addObject:description]; for (NSString *s in [child.node debugDescriptionComponents]) { [childrenDebugDescriptions addObject:[@" " stringByAppendingString:s]]; } } } return childrenDebugDescriptions; } static NSString *formatKeys(const std::vector<id<NSObject>> &keys) { NSMutableArray<NSString *> *a = [NSMutableArray new]; for (auto key : keys) { [a addObject:[key description] ?: @"(null)"]; } return [a componentsJoinedByString:@", "]; } /** Returns a multi-line string describing this node and its children nodes */ - (NSString *)debugDescription { return [[self debugDescriptionNodes] componentsJoinedByString:@"\n"]; } #endif @end id CKTreeNodeEmptyState(void) { static dispatch_once_t onceToken; static id emptyState; dispatch_once(&onceToken, ^{ emptyState = [NSObject new]; }); return emptyState; }