ComponentKit/Core/CKComponentController.mm (309 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 "CKComponentController.h" #import "CKComponentControllerInternal.h" #import <mutex> #import <RenderCore/RCAssert.h> #import <ComponentKit/CKGlobalConfig.h> #import <ComponentKit/CKInternalHelpers.h> #import "CKComponentInternal.h" #import "CKComponentSubclass.h" typedef NS_ENUM(NSInteger, CKComponentControllerState) { CKComponentControllerStateUnmounted = 0, CKComponentControllerStateMounting, CKComponentControllerStateMounted, CKComponentControllerStateRemounting, CKComponentControllerStateUnmounting, }; #if CK_ASSERTIONS_ENABLED typedef NS_ENUM(NSInteger, CKComponentControllerLifecycleState) { CKComponentControllerAllocated = 0, CKComponentControllerInitialized, CKComponentControllerInvalidated, }; #endif #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" static NSString *componentStateName(CKComponentControllerState state) { switch (state) { case CKComponentControllerStateUnmounted: return @"unmounted"; case CKComponentControllerStateMounting: return @"mounting"; case CKComponentControllerStateMounted: return @"mounted"; case CKComponentControllerStateRemounting: return @"remounting"; case CKComponentControllerStateUnmounting: return @"unmounting"; }; } #pragma clang diagnostic pop @implementation CKComponentController { CKComponentControllerState _state; BOOL _updatingComponent; __weak CKComponent *_component; // Protects `_component` and `_latestComponent` when `threadSafe_component` is called. std::mutex _componentMutex; #if CK_ASSERTIONS_ENABLED __weak NSThread *_initializationThread; CKComponentControllerLifecycleState _lifecycleState; #endif } - (instancetype)initWithComponent:(CKComponent *)component { if (self = [super init]) { _component = component; #if CK_ASSERTIONS_ENABLED _initializationThread = [NSThread currentThread]; #endif } return self; } - (void)dealloc { #if CK_ASSERTIONS_ENABLED RCWarn( _lifecycleState == CKComponentControllerInvalidated || _lifecycleState == CKComponentControllerAllocated || (_lifecycleState == CKComponentControllerInitialized && !CKSubclassOverridesInstanceMethod([CKComponentController class], self.class, @selector(invalidateController))), @"Dealloc called but controller (%@) was: %td", self.class, _lifecycleState); #endif } - (void)setLatestComponent:(CKComponent *)latestComponent { RCAssertMainThread(); if (latestComponent != _latestComponent) { [self willUpdateComponent]; if (self.shouldAcquireLockWhenUpdatingComponent) { std::lock_guard<std::mutex> lock(_componentMutex); _latestComponent = latestComponent; _updatingComponent = YES; } else { _latestComponent = latestComponent; _updatingComponent = YES; } } } - (CKComponent *)component { #if CK_ASSERTIONS_ENABLED if (_initializationThread != [NSThread currentThread]) { RCAssertWithCategory([NSThread isMainThread], NSStringFromClass(self.class), @"`self.component` must be called on the main thread"); } #endif const auto component = _component ?: _latestComponent; RCWarn(component != nil, @"`nil` component shouldn't be returned"); return component; } - (CKComponent *)threadSafe_component { CKComponent *component = nil; if ([NSThread isMainThread]) { component = _component ?: _latestComponent; } else { RCAssert(self.shouldAcquireLockWhenUpdatingComponent, @"threadSafe_component should only be called when updating component is thread safe as well"); std::lock_guard<std::mutex> lock(_componentMutex); component = _component ?: _latestComponent; } RCWarn(component != nil, @"`nil` component shouldn't be returned"); return component; } - (BOOL)shouldAcquireLockWhenUpdatingComponent { return NO; } - (void)didInit { RCAssertMainThread(); #if CK_ASSERTIONS_ENABLED RCWarn(_lifecycleState == CKComponentControllerAllocated, @"Did init called but controller (%@) was: %td", self.class, _lifecycleState); _lifecycleState = CKComponentControllerInitialized; #endif } - (void)willMount { RCAssertMainThread(); } - (void)didMount { RCAssertMainThread(); } - (void)willRemount { RCAssertMainThread(); } - (void)didRemount { RCAssertMainThread(); } - (void)willUnmount { RCAssertMainThread(); } - (void)didUnmount { RCAssertMainThread(); } - (void)willUpdateComponent { RCAssertMainThread(); } - (void)didUpdateComponent { RCAssertMainThread(); } - (void)componentWillRelinquishView { RCAssertMainThread(); } - (void)componentDidAcquireView { RCAssertMainThread(); } - (void)componentTreeWillAppear { RCAssertMainThread(); } - (void)componentTreeDidDisappear { RCAssertMainThread(); } - (void)invalidateController { RCAssertMainThread(); #if CK_ASSERTIONS_ENABLED RCWarnWithCategory(_lifecycleState == CKComponentControllerInitialized || (_lifecycleState == CKComponentControllerAllocated && !CKSubclassOverridesInstanceMethod([CKComponentController class], self.class, @selector(didInit))), self.component.className, @"Invalidate called but controller (%@) was: %td", self.class, _lifecycleState); _lifecycleState = CKComponentControllerInvalidated; #endif } - (void)didPrepareLayout:(const RCLayout &)layout forComponent:(CKComponent *)component {} #pragma mark - Hooks - (void)willStartUpdateToComponent:(CKComponent *)component { // We need to check `_updatingComponent` so that `willUpdateComponent` will be triggered if `_latestComponent` // is not updated after component build. if (!_updatingComponent) { if (component != _component) { [self willUpdateComponent]; if (self.shouldAcquireLockWhenUpdatingComponent) { std::lock_guard<std::mutex> lock(_componentMutex); _component = component; _updatingComponent = YES; } else { _component = component; _updatingComponent = YES; } } } else { if (self.shouldAcquireLockWhenUpdatingComponent) { std::lock_guard<std::mutex> lock(_componentMutex); _component = component; } else { _component = component; } } } - (void)didFinishComponentUpdate { if (_updatingComponent) { [self didUpdateComponent]; _updatingComponent = NO; } } - (void)componentWillMount:(CKComponent *)component { [self willStartUpdateToComponent:component]; switch (_state) { case CKComponentControllerStateUnmounted: _state = CKComponentControllerStateMounting; [self willMount]; break; case CKComponentControllerStateMounted: _state = CKComponentControllerStateRemounting; [self willRemount]; break; default: RCCAssertWithCategory(NO, NSStringFromClass([self class]), @"Unexpected state '%@' for %@", componentStateName(_state), [_component class]); } } - (void)componentDidMount:(CKComponent *)component { switch (_state) { case CKComponentControllerStateMounting: _state = CKComponentControllerStateMounted; [self didMount]; break; case CKComponentControllerStateRemounting: _state = CKComponentControllerStateMounted; [self didRemount]; break; default: RCCAssertWithCategory(NO, NSStringFromClass([self class]), @"Unexpected state '%@' for %@", componentStateName(_state), [_component class]); } [self didFinishComponentUpdate]; } - (void)componentWillUnmount:(CKComponent *)component { switch (_state) { case CKComponentControllerStateMounted: // The "old" version of a component may be unmounted after the new version has finished remounting. if (component == _component) { _state = CKComponentControllerStateUnmounting; [self willUnmount]; } break; case CKComponentControllerStateRemounting: RCAssert(component != _component, @"Didn't expect the new component to be unmounting during remount"); break; default: RCCAssertWithCategory(NO, NSStringFromClass([self class]), @"Unexpected state '%@' for %@", componentStateName(_state), [_component class]); } } - (void)componentDidUnmount:(CKComponent *)component { switch (_state) { case CKComponentControllerStateUnmounting: RCAssert(component == _component, @"Unexpected component mismatch during unmount from unmounting"); _state = CKComponentControllerStateUnmounted; [self didUnmount]; break; case CKComponentControllerStateRemounting: RCAssert(component != _component, @"Didn't expect the new component to be unmounted during remount"); break; case CKComponentControllerStateMounted: RCAssert(component != _component, @"Didn't expect the new component to be unmounted while mounted"); break; default: RCCAssertWithCategory(NO, NSStringFromClass([self class]), @"Unexpected state '%@' for %@", componentStateName(_state), [_component class]); } } - (void)_relinquishView { [self componentWillRelinquishView]; _view = nil; } - (void)component:(CKComponent *)component willRelinquishView:(UIView *)view { if (component == _component) { RCAssert(view == _view, @"Didn't expect to be relinquishing view %@ when _view is %@", view, _view); [self _relinquishView]; } } - (void)component:(CKComponent *)component didAcquireView:(UIView *)view { if (component == _component) { if (view != _view) { if (_view) { RCAssert(_updatingComponent, @"Only expect to acquire a new view before relinquishing old if updating"); [self _relinquishView]; } _view = view; [self componentDidAcquireView]; } } } - (id)nextResponder { return [_component nextResponderAfterController]; } - (id)targetForAction:(SEL)action withSender:(id)sender { return [self canPerformAction:action withSender:sender] ? self : [[self nextResponder] targetForAction:action withSender:sender]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return [self respondsToSelector:action]; } @end