ComponentKit/Core/Scope/CKComponentScopeHandle.mm (226 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 "CKComponentScopeHandle.h" #include <atomic> #include <mutex> #import <ComponentKit/CKInternalHelpers.h> #import <ComponentKit/CKTreeNode.h> #import <ComponentKit/CKMutex.h> #import "CKComponentScopeRoot.h" #import "CKComponentSubclass.h" #import "CKComponentInternal.h" #import "CKComponentProtocol.h" #import "CKComponentControllerProtocol.h" #import "CKThreadLocalComponentScope.h" #import "CKRenderComponentProtocol.h" @interface CKScopedResponder () - (void)addHandleToChain:(CKComponentScopeHandle *)component; @end @implementation CKComponentScopeHandle { id<CKComponentStateListener> __weak _listener; id<CKComponentControllerProtocol> _controller; CKComponentScopeRootIdentifier _rootIdentifier; BOOL _acquired; BOOL _resolved; CKScopedResponder *_scopedResponder; } - (instancetype)initWithListener:(id<CKComponentStateListener>)listener rootIdentifier:(CKComponentScopeRootIdentifier)rootIdentifier componentTypeName:(const char *)componentTypeName initialState:(id)initialState { static std::atomic_int32_t nextGlobalIdentifier; return [self initWithListener:listener globalIdentifier:++nextGlobalIdentifier rootIdentifier:rootIdentifier componentTypeName:componentTypeName state:initialState controller:nil // Controllers are built on resolution of the handle. scopedResponder:nil]; // Scoped responders are created lazily. Once they exist, we use that reference for future handles. } - (instancetype)initWithListener:(id<CKComponentStateListener>)listener globalIdentifier:(CKComponentScopeHandleIdentifier)globalIdentifier rootIdentifier:(CKComponentScopeRootIdentifier)rootIdentifier componentTypeName:(const char *)componentTypeName state:(id)state controller:(id<CKComponentControllerProtocol>)controller scopedResponder:(CKScopedResponder *)scopedResponder { if (self = [super init]) { _listener = listener; _globalIdentifier = globalIdentifier; _rootIdentifier = rootIdentifier; _componentTypeName = componentTypeName; _state = state; _controller = controller; _scopedResponder = scopedResponder; [scopedResponder addHandleToChain:self]; } return self; } - (instancetype)newStatelessHandle { const auto handle = [[CKComponentScopeHandle alloc] initWithListener:_listener globalIdentifier:_globalIdentifier rootIdentifier:_rootIdentifier componentTypeName:_componentTypeName state:nil controller:nil scopedResponder:nil]; // This handle isn't meant to be resolved, marking as `YES`. handle->_resolved = YES; return handle; } - (instancetype)newHandleWithStateUpdates:(const CKComponentStateUpdateMap &)stateUpdates { id updatedState = _state; const auto pendingUpdatesIt = stateUpdates.find(self); if (pendingUpdatesIt != stateUpdates.end()) { for (auto pendingUpdate: pendingUpdatesIt->second) { if (pendingUpdate != nil) { updatedState = pendingUpdate(updatedState); } } } return [[CKComponentScopeHandle alloc] initWithListener:_listener globalIdentifier:_globalIdentifier rootIdentifier:_rootIdentifier componentTypeName:_componentTypeName state:updatedState controller:_controller scopedResponder:_scopedResponder]; } - (id<CKComponentControllerProtocol>)controller { RCAssert(_resolved, @"Requesting controller from scope handle before resolution. The controller will be nil."); return _controller; } - (void)dealloc { RCAssert(_resolved, @"Must be resolved before deallocation."); } #pragma mark - State - (void)updateState:(id (^)(id))updateBlock metadata:(const CKStateUpdateMetadata &)metadata mode:(CKUpdateMode)mode { RCAssertNotNil(updateBlock, @"The update block cannot be nil"); if (![NSThread isMainThread] && [(id<CKComponentStateListener>)[_listener class] requiresMainThreadAffinedStateUpdates]) { // Passing a const& into a block is scary, make a local copy to be safe. const auto metadataCopy = metadata; dispatch_async(dispatch_get_main_queue(), ^{ [self updateState:updateBlock metadata:metadataCopy mode:mode]; }); return; } [_listener componentScopeHandle:self rootIdentifier:_rootIdentifier didReceiveStateUpdate:updateBlock metadata:metadata mode:mode]; } - (void)replaceState:(id)state { RCAssertFalse(_resolved); _state = state; } #pragma mark - Component Scope Handle Acquisition - (BOOL)acquireFromComponent:(id<CKComponentProtocol>)component { if (!_acquired && component.typeName == _componentTypeName) { _acquired = YES; _acquiredComponent = component; return YES; } else { return NO; } } - (void)relinquishComponent { _acquiredComponent = nil; } - (void)forceAcquireFromComponent:(id<CKComponentProtocol>)component { RCAssert(component.typeName == _componentTypeName, @"%s has to be a member of %s class", component.typeName, _componentTypeName); RCAssert(!_acquired, @"scope handle cannot be acquired twice"); _acquired = YES; _acquiredComponent = component; } - (void)setTreeNode:(CKTreeNode *)treeNode { RCAssertWithCategory(_treeNodeIdentifier == 0, NSStringFromClass([_acquiredComponent class]), @"_treeNodeIdentifier cannot be set twice"); _treeNodeIdentifier = treeNode.nodeIdentifier; _treeNode = treeNode; } - (void)resolveAndRegisterInScopeRoot:(CKComponentScopeRoot *)scopeRoot { [self resolveInScopeRoot:scopeRoot]; [self registerInScopeRoot:scopeRoot]; } - (void)resolveInScopeRoot:(CKComponentScopeRoot *)scopeRoot { RCAssertFalse(_resolved); // Strong ref: _acquiredComponent may be nil when rendering-to-nil as the // handle won't be acquired. const auto acquiredComponent = _acquiredComponent; if (acquiredComponent != nil && _controller == nil) { // Build the controller on the first non nil component. _controller = [acquiredComponent buildController]; } _resolved = YES; } - (void)registerInScopeRoot:(CKComponentScopeRoot *)scopeRoot { // Register after scope handle resolution so the controller can be accessed // in the predicates. [scopeRoot registerComponent:_acquiredComponent]; [scopeRoot registerComponentController:_controller]; } - (CKScopedResponder *)scopedResponder { if (!_scopedResponder) { _scopedResponder = [CKScopedResponder new]; [_scopedResponder addHandleToChain:self]; } return _scopedResponder; } @end @implementation CKScopedResponder { std::vector<__weak CKComponentScopeHandle *> _handles; std::mutex _mutex; } - (void)addHandleToChain:(CKComponentScopeHandle *)handle { if (!handle) { return; } std::lock_guard<std::mutex> l(_mutex); _handles.push_back(handle); } - (CKScopedResponderKey)keyForHandle:(CKComponentScopeHandle *)handle { static const CKScopedResponderKey notFoundKey = INT_MAX; if (handle == nil) { return notFoundKey; } std::lock_guard<std::mutex> l(_mutex); auto result = CK::find(_handles, handle); if (result == _handles.end()) { RCFailAssert(@"This scope handle is not associated with this Responder."); return notFoundKey; } // Returning the index of an element in a vector: https://stackoverflow.com/a/15099743 return (int)std::distance(_handles.begin(), result); } - (id)responderForKey:(CKScopedResponderKey)key { std::lock_guard<std::mutex> l(_mutex); const size_t numberOfHandles = _handles.size(); if (key < 0 || key >= numberOfHandles) { RCFailAssert(@"Invalid key \"%d\" for responder with %zu handles", key, numberOfHandles); return nil; } for (int i = key; i < numberOfHandles; i++) { const auto handle = _handles[i]; const id<CKComponentProtocol> responder = handle.acquiredComponent; if (responder != nil) { return responder; } } return nil; } @end