ComponentKit/Core/Swift/CKSwiftComponent.mm (340 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 "CKSwiftComponent.h" #import "CKComponentViewConfiguration_SwiftBridge+Internal.h" #import "RCComponentSize_SwiftBridge+Internal.h" #import "CKIterableHelpers.h" #import <ComponentKit/CKComponentSubclass.h> #import <ComponentKit/CKComponentInternal.h> #import <ComponentKit/CKInternalHelpers.h> #import <ComponentKit/CKFlexboxComponent.h> #import <ComponentKit/CKThreadLocalComponentScope.h> #import <ComponentKit/CKIdValueWrapper.h> #import <ComponentKit/CKAnimationComponentPassthroughView.h> #import <RenderCore/RCAssert.h> @interface CKSwiftComponentController : CKComponentController @end @implementation CKSwiftComponentModel_SwiftBridge { @package CAAnimation *_animation; CAAnimation *_initialMountAnimation; CAAnimation *_finalUnmountAnimation; NSArray<CKSwiftComponentDidInitCallback> *_didInitCallbacks; NSArray<CKSwiftComponentWillMountCallback> *_willMountCallbacks; NSArray<CKSwiftComponentDidUnMountCallback> *_didUnmountCallbacks; NSArray<CKSwiftComponentWillDisposeCallback> *_willDisposeCallbacks; } - (instancetype)initWithAnimation:(CAAnimation *)animation initialMountAnimation:(CAAnimation *)initialMountAnimation finalUnmountAnimation:(CAAnimation *)finalUnmountAnimation didInitCallbacks:(NSArray<CKSwiftComponentDidInitCallback> *)didInitCallbacks willMountCallbacks:(NSArray<CKSwiftComponentWillMountCallback> *)willMountCallbacks didUnmountCallbacks:(NSArray<CKSwiftComponentDidUnMountCallback> *)didUnmountCallbacks willDisposeCallbacks:(NSArray<CKSwiftComponentWillDisposeCallback> *)willDisposeCallbacks { if (self = [super init]) { _animation = animation; _initialMountAnimation = initialMountAnimation; _finalUnmountAnimation = finalUnmountAnimation; _didInitCallbacks = didInitCallbacks; _willMountCallbacks = willMountCallbacks; _didUnmountCallbacks = didUnmountCallbacks; _willDisposeCallbacks = willDisposeCallbacks; } return self; } - (BOOL)requiresController { return _didInitCallbacks.firstObject != nil || _willMountCallbacks.firstObject != nil || _didUnmountCallbacks.firstObject != nil || _willDisposeCallbacks.firstObject != nil; } - (BOOL)hasAnimations { return _animation != nil || _initialMountAnimation != nil || _finalUnmountAnimation != nil; } @end @implementation CKSwiftComponent { // The child. When nil, CKSwiftComponent is a leaf component. CKComponent *_child; @package CKSwiftComponentModel_SwiftBridge *_model; } static CKComponentViewConfiguration _viewConfigurationWithViewIfAnimated( CKComponentViewConfiguration_SwiftBridge *swiftView, BOOL hasAnimations) { if (swiftView == nil) { return hasAnimations ? CKComponentViewConfiguration{CKAnimationComponentPassthroughView.class} : CKComponentViewConfiguration{}; } else { return swiftView.viewConfig.forceViewClassIfNone(CKAnimationComponentPassthroughView.class); } } - (instancetype)initFromShellComponent:(CKSwiftComponent *)shellComponent child:(CKComponent *)child { if (self = [super initWithView:shellComponent.viewConfiguration size:shellComponent.size]) { _model = shellComponent->_model; _child = child; } return self; } - (instancetype)initWithSwiftView:(CKComponentViewConfiguration_SwiftBridge *)swiftView swiftSize:(RCComponentSize_SwiftBridge *_Nullable)swiftSize child:(CKComponent *)child model:(CKSwiftComponentModel_SwiftBridge *)model { const auto size = swiftSize != nil ? swiftSize.componentSize : RCComponentSize{}; const auto view = _viewConfigurationWithViewIfAnimated(swiftView, model.hasAnimations); if (self = [super initWithView:view size:size]) { _model = model; _child = child; } return self; } - (RCLayout)computeLayoutThatFits:(CKSizeRange)constrainedSize restrictedToSize:(const RCComponentSize &)size relativeToParentSize:(CGSize)parentSize { if (_child) { // Non leaf component RCAssert(size == RCComponentSize(), @"CKSwiftComponent only passes size {} to the super class initializer, but received size %@ " "(component=%@)", size.description(), _child); const auto l = [_child layoutThatFits:constrainedSize parentSize:parentSize]; const auto lSize = l.size; return {self, lSize, {{{0,0}, std::move(l)}}}; } else { // Leaf component return [super computeLayoutThatFits:constrainedSize restrictedToSize:size relativeToParentSize:parentSize]; } } - (UIView *)viewForAnimation { // Delegate to the wrapped component's viewForAnimation if we don't have one. return [super viewForAnimation] ?: [_child viewForAnimation]; } - (unsigned int)numberOfChildren { return RCIterable::numberOfChildren(_child); } - (id<CKMountable>)childAtIndex:(unsigned int)index { return RCIterable::childAtIndex(self, index, _child); } - (std::vector<CKComponentAnimation>)animationsFromPreviousComponent:(CKComponent *)previousComponent { if (_model != nil && _model->_animation != nil) { return { {self, _model->_animation}, }; } else { return {}; } } - (std::vector<CKComponentAnimation>)animationsOnInitialMount { if (_model != nil && _model->_initialMountAnimation != nil) { return { {self, _model->_initialMountAnimation}, }; } else { return {}; } } - (std::vector<CKComponentFinalUnmountAnimation>)animationsOnFinalUnmount { if (_model != nil && _model->_finalUnmountAnimation != nil) { return { {self, _model->_finalUnmountAnimation}, }; } else { return {}; } } - (BOOL)hasAnimations { return _model != nil && _model->_animation != nil; } - (BOOL)hasBoundsAnimations { return NO; } - (BOOL)hasInitialMountAnimations { return _model != nil && _model->_initialMountAnimation != nil; } - (BOOL)hasFinalUnmountAnimations { return _model != nil && _model->_finalUnmountAnimation != nil; } - (id<CKComponentControllerProtocol>)buildController { if (_model.requiresController) { return [[CKSwiftComponentController alloc] initWithComponent:self]; } else { return nil; } } + (Class<CKComponentControllerProtocol>)controllerClass { // Avoid assert in super return nil; } @end @implementation CKSwiftComponentController { CKSwiftComponentModel_SwiftBridge *_model; } - (instancetype)initWithComponent:(CKSwiftComponent *)component { if (component->_model == nil) { RCFailAssert(@"Building controller without model"); return nil; } if (self = [super initWithComponent:component]) { _model = component->_model; } return self; } - (void)didInit { // TODO: Predicate for initialization [super didInit]; for (CKSwiftComponentDidInitCallback const callback : _model->_didInitCallbacks) { callback(); } } - (void)willMount { [super willMount]; for (CKSwiftComponentWillMountCallback const callback : _model->_willMountCallbacks) { callback(); } } - (void)didUnmount { [super didUnmount]; for (CKSwiftComponentDidUnMountCallback const callback : _model->_didUnmountCallbacks) { callback(); } } - (void)invalidateController { // TODO: Predicate for invalidation [super invalidateController]; for (CKSwiftComponentWillDisposeCallback const callback : _model->_willDisposeCallbacks) { callback(); } } @end @interface CKSwiftStateWrapper : NSObject { @package std::vector<id> _values; } @end @implementation CKSwiftStateWrapper - (instancetype)initWithValues:(std::vector<id>)values { if (self = [super init]) { _values = std::move(values); } return self; } - (void)add:(id)value { _values.push_back(value); } - (instancetype)newStateWrapperWithUpdatedValue:(id)updatedValue atIndex:(NSInteger)index { CKSwiftStateWrapper *const copy = [[self.class alloc] initWithValues:_values]; copy->_values[index] = updatedValue; return copy; } @end static CKComponentScopePair *CKSwiftGetCurrentPair() { const auto threadLocalScope = CKThreadLocalComponentScope::currentScope(); if (threadLocalScope == nullptr || threadLocalScope->stack.size() <= 1) { RCCFailAssert(@"No TLS on get node"); return nil; } else { return &threadLocalScope->stack.top(); } } void CKSwiftPopClass() { const auto threadLocalScope = CKThreadLocalComponentScope::currentScope(); if (threadLocalScope == nullptr) { RCCFailAssert(@"No TLS on class pop"); return; } const auto& pair = *CKSwiftGetCurrentPair(); [pair.node.scopeHandle resolveInScopeRoot:threadLocalScope->newScopeRoot]; threadLocalScope->pop(YES, YES); } CKTreeNode *CKSwiftCreateNode(Class klass, id identifier) { const auto threadLocalScope = CKThreadLocalComponentScope::currentScope(); if (threadLocalScope == nullptr) { RCCFailAssert(@"Create scope handle but no TLS"); return nil; } const auto childPair = [CKTreeNode childPairForPair:threadLocalScope->stack.top() newRoot:threadLocalScope->newScopeRoot componentTypeName:class_getName(klass) identifier:identifier keys:threadLocalScope->keys.top() initialStateCreator:^{ return [CKSwiftStateWrapper new]; } stateUpdates:threadLocalScope->stateUpdates requiresScopeHandle:YES]; threadLocalScope->push(childPair, YES, /* ancestor has state update */YES); return childPair.node; } BOOL CKSwiftInitializeState(CKTreeNode *node, NSInteger index, NS_NOESCAPE id _Nullable (^initialValueProvider)()) { const auto pair = CKSwiftGetCurrentPair(); if (pair == nullptr) { RCCFailAssert(@"Initialising state but pair is nil"); return NO; } if (pair->previousNode == nil) { const auto handle = node.scopeHandle; RCCAssert([handle.state isKindOfClass:CKSwiftStateWrapper.class], @"Unexpected state: %@", handle.state); const auto wrapper = (CKSwiftStateWrapper *)handle.state; [wrapper add:initialValueProvider()]; return YES; } else { return NO; } } id CKSwiftFetchState(CKTreeNode *node, NSInteger index) { RCCAssert(CKThreadLocalComponentScope::currentScope() != nullptr || NSThread.currentThread.isMainThread, @"Fetching state out of the main thread (or body) non permitted"); const auto stateWrapper = (CKSwiftStateWrapper *)node.scopeHandle.state; return stateWrapper->_values[index]; } void CKSwiftUpdateViewModelState(CKComponentScopeHandle *scopeHandle) { RCCAssert(NSThread.currentThread.isMainThread, @"Updating state out of the main thread not permitted"); RCCAssert(CKThreadLocalComponentScope::currentScope() == nullptr, @"Updating state during build not permitted"); [scopeHandle updateState:^id _Nullable(id state) { return state; } metadata:{} mode:CKUpdateModeAsynchronous]; } void CKSwiftUpdateState(CKTreeNode *node, NSInteger index, id _Nullable newValue) { RCCAssert(NSThread.currentThread.isMainThread, @"Updating state out of the main thread not permitted"); RCCAssert(CKThreadLocalComponentScope::currentScope() == nullptr, @"Updating state during build not permitted"); [node.scopeHandle updateState:^id _Nullable(CKSwiftStateWrapper *state) { return [state newStateWrapperWithUpdatedValue:newValue atIndex:index]; } metadata:{} mode:CKUpdateModeAsynchronous]; } BOOL CKSwiftInitializeAction(Class klass, CKScopedResponder **responder, CKScopedResponderKey *key) { const auto pair = CKSwiftGetCurrentPair(); if (responder == nil || key == nil) { RCCFailAssert(@"Initialising action but passing nil responder/key"); return NO; } if (pair == nullptr) { RCCFailAssert(@"Initialising action but pair is nil"); return NO; } const auto handle = pair->node.scopeHandle; if (class_getName(klass) != handle.componentTypeName) { RCCFailAssert(@"Creating an action outside the view's body function. Expected: %@, Found: %s", klass, handle.componentTypeName); return NO; } *responder = handle.scopedResponder; *key = [handle.scopedResponder keyForHandle:handle]; return YES; }