ComponentKit/Core/ComponentContext/CKComponentContextHelper.mm (162 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 "CKComponentContextHelper.h" #import <RenderCore/RCAssert.h> #import <ComponentKit/CKComponentScopeRoot.h> #import <ComponentKit/CKThreadLocalComponentScope.h> #import <ComponentKit/CKRootTreeNode.h> #import <stack> static NSString *const kThreadDictionaryKey = @"CKComponentContext"; struct CKComponentContextStackItem { NSMutableDictionary *dictionary; BOOL itemWasAdded; }; @interface CKComponentContextValue : NSObject { @public // The main store. NSMutableDictionary *_dictionary; // A map between render component to its dictionary. NSMapTable<id, NSMutableDictionary *> *_renderToDictionaryCache; // Stack of previous store dictionaries. std::stack<CKComponentContextStackItem> _stack; // Dirty flag for the current store in use. BOOL _itemWasAdded; } @end @implementation CKComponentContextValue @end static CKComponentContextValue *contextValue(BOOL create) { NSMutableDictionary *const threadDictionary = [[NSThread currentThread] threadDictionary]; CKComponentContextValue *contextValue = threadDictionary[kThreadDictionaryKey]; if (contextValue == nil && create) { contextValue = [CKComponentContextValue new]; contextValue->_dictionary = [NSMutableDictionary dictionary]; contextValue->_renderToDictionaryCache = [NSMapTable weakToStrongObjectsMapTable]; threadDictionary[kThreadDictionaryKey] = contextValue; } return contextValue; } bool CKComponentContextContents::operator==(const CKComponentContextContents &other) const { return ((other.objects == nil && objects == nil) || [other.objects isEqualToDictionary:objects]); } bool CKComponentContextContents::operator!=(const CKComponentContextContents &other) const { return !(*this == other); } static void clearContextValueIfEmpty(CKComponentContextValue *const currentValue) { if ([currentValue->_dictionary count] == 0 && currentValue->_renderToDictionaryCache.count == 0) { [[[NSThread currentThread] threadDictionary] removeObjectForKey:kThreadDictionaryKey]; } } CKComponentContextPreviousState CKComponentContextHelper::store(id key, id object) noexcept { CKComponentContextValue *v = contextValue(YES); NSMutableDictionary *const c = v->_dictionary; id originalValue = c[key]; c[key] = object; v->_itemWasAdded = YES; CKComponentContextPreviousState state = {.key = key, .originalValue = originalValue, .newValue = object}; return state; } void CKComponentContextHelper::restore(const CKComponentContextPreviousState &storeResult) noexcept { // We want to create the context dictionary if it doesn't exist already, because we need to restore the original // value. In practice it should always exist already except for an obscure edge case; see the unit test // testTriplyNestedComponentContextWithNilMiddleValueCorrectlyRestoresOuterValue for an example. CKComponentContextValue *const v = contextValue(YES); NSMutableDictionary *const c = v->_dictionary; id<NSCopying> storeResultKey = (id<NSCopying>)storeResult.key; RCCAssert(c[storeResultKey] == storeResult.newValue, @"Context value for %@ unexpectedly mutated", storeResult.key); c[storeResultKey] = storeResult.originalValue; clearContextValueIfEmpty(v); } void CKComponentContextHelper::didCreateRenderComponent(id component) noexcept { CKComponentContextValue *const v = contextValue(NO); if (!v) { return; } // Make a backup dictionary if needed and store it in the _renderToDictionaryCache map. if (v->_itemWasAdded) { NSMutableDictionary *renderDictionary = [v->_dictionary mutableCopy]; [v->_renderToDictionaryCache setObject:renderDictionary forKey:component]; } } void CKComponentContextHelper::willBuildComponentTree(id component) noexcept { CKComponentContextValue *const v = contextValue(NO); if (!v) { return; } NSMutableDictionary *renderDictionary = [v->_renderToDictionaryCache objectForKey:component]; if (renderDictionary) { // Push the current store into the stack. v->_stack.push({ .dictionary = v->_dictionary, .itemWasAdded = v->_itemWasAdded, }); // Update the pointer to the latest render dictionary v->_dictionary = renderDictionary; v->_itemWasAdded = NO; } } void CKComponentContextHelper::didBuildComponentTree(id component) noexcept { CKComponentContextValue *const v = contextValue(NO); if (!v) { return; } NSMutableDictionary *renderDictionary = [v->_renderToDictionaryCache objectForKey:component]; if (renderDictionary) { RCCAssert(!v->_stack.empty(), @"The stack cannot be empty if there is a render dictionary in the cache"); RCCAssert(v->_dictionary == renderDictionary, @"The current store is different than the renderDictionary"); // Update the pointer to the latest render dictionary if (!v->_stack.empty()) { // Retrieve the previous value from the stack. auto const &topItem = v->_stack.top(); v->_dictionary = topItem.dictionary; v->_itemWasAdded = topItem.itemWasAdded; // Pop the top backup from the stack v->_stack.pop(); // Remove the dictionary from the map [v->_renderToDictionaryCache removeObjectForKey:component]; } clearContextValueIfEmpty(v); } } id CKComponentContextHelper::fetchMutable(id key) noexcept { CKComponentContextValue *const v = contextValue(NO); if (v) { // Props updates support. CKThreadLocalComponentScope *currentScope = CKThreadLocalComponentScope::currentScope(); if (currentScope != nullptr) { [currentScope->newScopeRoot rootNode].markTopRenderComponentAsDirtyForPropsUpdates(); } return v->_dictionary[key]; } return nil; } id CKComponentContextHelper::fetch(id key) noexcept { CKComponentContextValue *const v = contextValue(NO); if (v) { return v->_dictionary[key]; } return nil; } CKComponentContextContents CKComponentContextHelper::fetchAll() noexcept { CKComponentContextValue *const v = contextValue(NO); if (!v) { return {}; } return { .objects = [v->_dictionary copy], }; } NSMutableDictionary<Class, id>* CKComponentInitialValuesContext::setInitialValues(NSDictionary<Class, id> *objects) noexcept { CKComponentContextValue *const v = contextValue(YES); // Save the old values. auto const oldObjects = v->_dictionary; // Copy the new values. v->_dictionary = [objects mutableCopy]; // Move the old values back to the main storage. for (id key in oldObjects) { v->_dictionary[key] = oldObjects[key]; } return oldObjects; } void CKComponentInitialValuesContext::cleanInitialValues(NSMutableDictionary<Class, id> *oldObjects) noexcept { CKComponentContextValue *const v = contextValue(NO); if (!v) { return; } v->_dictionary = oldObjects; clearContextValueIfEmpty(v); }