From 41cf6da074e055ce6c0a6c2f3e0c1feeb5166ec4 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Fri, 1 Nov 2019 09:35:40 -0700 Subject: [PATCH] Fabric: Implementation of `RCTMountingTransactionObserving` protocol Summary: This diff finally uses all facilities from the previous diffs to build an implementation of `RCTMountingTransactionObserving` protocol which does *not* require using expensive Objective-C runtime features. In the coming diffs, we will see how it can/should be used. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D18217101 fbshipit-source-id: 34f411dcb527dc81570c2f2833ce13b40e1450db --- .../RCTComponentViewClassDescriptor.h | 7 ++ .../Mounting/RCTComponentViewDescriptor.h | 25 +++++++ .../Mounting/RCTComponentViewFactory.mm | 22 +++++- .../Mounting/RCTComponentViewRegistry.mm | 12 ++++ React/Fabric/Mounting/RCTMountingManager.mm | 41 ++++++++--- ...CTMountingTransactionObserverCoordinator.h | 42 +++++++++++ ...TMountingTransactionObserverCoordinator.mm | 72 +++++++++++++++++++ .../mounting/MountingTransactionMetadata.h | 2 +- 8 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.h create mode 100644 React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.mm diff --git a/React/Fabric/Mounting/RCTComponentViewClassDescriptor.h b/React/Fabric/Mounting/RCTComponentViewClassDescriptor.h index 52a6cc7d9cc..c235d42a7c1 100644 --- a/React/Fabric/Mounting/RCTComponentViewClassDescriptor.h +++ b/React/Fabric/Mounting/RCTComponentViewClassDescriptor.h @@ -20,6 +20,13 @@ class RCTComponentViewClassDescriptor final { * Associated (and owned) native view class. */ Class viewClass; + + /* + * Indicates a requirement to call on the view methods from + * `RCTMountingTransactionObserving` protocol. + */ + bool observesMountingTransactionWillMount{false}; + bool observesMountingTransactionDidMount{false}; }; NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/RCTComponentViewDescriptor.h b/React/Fabric/Mounting/RCTComponentViewDescriptor.h index 14c5a39083e..1642c2bc51d 100644 --- a/React/Fabric/Mounting/RCTComponentViewDescriptor.h +++ b/React/Fabric/Mounting/RCTComponentViewDescriptor.h @@ -22,6 +22,31 @@ class RCTComponentViewDescriptor final { * Associated (and owned) native view instance. */ UIView *view; + + /* + * Indicates a requirement to call on the view methods from + * `RCTMountingTransactionObserving` protocol. + */ + bool observesMountingTransactionWillMount{false}; + bool observesMountingTransactionDidMount{false}; +}; + +inline bool operator==(RCTComponentViewDescriptor const &lhs, RCTComponentViewDescriptor const &rhs) +{ + return lhs.view == rhs.view; +} + +inline bool operator!=(RCTComponentViewDescriptor const &lhs, RCTComponentViewDescriptor const &rhs) +{ + return lhs.view != rhs.view; +} + +template <> +struct std::hash { + size_t operator()(RCTComponentViewDescriptor const &componentViewDescriptor) const + { + return std::hash()((__bridge void *)componentViewDescriptor.view); + } }; NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/RCTComponentViewFactory.mm b/React/Fabric/Mounting/RCTComponentViewFactory.mm index e2ab8f91895..ef6c07e0f3c 100644 --- a/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -9,6 +9,7 @@ #import #import + #import #import @@ -21,6 +22,7 @@ #import "RCTImageComponentView.h" #import "RCTLegacyViewManagerInteropComponentView.h" #import "RCTModalHostViewComponentView.h" +#import "RCTMountingTransactionObserving.h" #import "RCTParagraphComponentView.h" #import "RCTPullToRefreshViewComponentView.h" #import "RCTRootComponentView.h" @@ -31,6 +33,8 @@ #import "RCTUnimplementedNativeComponentView.h" #import "RCTViewComponentView.h" +#import + using namespace facebook::react; @implementation RCTComponentViewFactory { @@ -79,7 +83,17 @@ using namespace facebook::react; - (RCTComponentViewClassDescriptor)_componentViewClassDescriptorFromClass:(Class)viewClass { - return RCTComponentViewClassDescriptor{.viewClass = viewClass}; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return RCTComponentViewClassDescriptor + { + .viewClass = viewClass, + .observesMountingTransactionWillMount = + (bool)class_respondsToSelector(viewClass, @selector(mountingTransactionWillMountWithMetadata:)), + .observesMountingTransactionDidMount = + (bool)class_respondsToSelector(viewClass, @selector(mountingTransactionDidMountWithMetadata:)), + }; +#pragma clang diagnostic pop } - (void)registerComponentViewClass:(Class)componentViewClass @@ -120,7 +134,11 @@ using namespace facebook::react; auto componentViewClassDescriptor = iterator->second; Class viewClass = componentViewClassDescriptor.viewClass; - return RCTComponentViewDescriptor{.view = [[viewClass alloc] init]}; + return RCTComponentViewDescriptor{ + .view = [[viewClass alloc] init], + .observesMountingTransactionWillMount = componentViewClassDescriptor.observesMountingTransactionWillMount, + .observesMountingTransactionDidMount = componentViewClassDescriptor.observesMountingTransactionDidMount, + }; } - (facebook::react::ComponentDescriptorRegistry::Shared)createComponentDescriptorRegistryWithParameters: diff --git a/React/Fabric/Mounting/RCTComponentViewRegistry.mm b/React/Fabric/Mounting/RCTComponentViewRegistry.mm index 23d08cef831..a274af78d45 100644 --- a/React/Fabric/Mounting/RCTComponentViewRegistry.mm +++ b/React/Fabric/Mounting/RCTComponentViewRegistry.mm @@ -77,6 +77,8 @@ const NSInteger RCTComponentViewRegistryRecyclePoolMaxSize = 1024; @implementation RCTComponentViewRegistry { better::map _registry; better::map> _recyclePool; + + std::unordered_set _mountingTransactionObservers; } - (instancetype)init @@ -133,6 +135,11 @@ const NSInteger RCTComponentViewRegistryRecyclePoolMaxSize = 1024; _registry.insert({tag, componentViewDescriptor}); + if (componentViewDescriptor.observesMountingTransactionDidMount || + componentViewDescriptor.observesMountingTransactionWillMount) { + _mountingTransactionObservers.insert(componentViewDescriptor); + } + #ifdef LEGACY_UIMANAGER_INTEGRATION_ENABLED [RCTUIManager registerView:componentViewDescriptor.view]; #endif @@ -153,6 +160,11 @@ const NSInteger RCTComponentViewRegistryRecyclePoolMaxSize = 1024; [RCTUIManager unregisterView:componentViewDescriptor.view]; #endif + if (componentViewDescriptor.observesMountingTransactionDidMount || + componentViewDescriptor.observesMountingTransactionWillMount) { + _mountingTransactionObservers.erase(componentViewDescriptor); + } + _registry.erase(tag); componentViewDescriptor.view.tag = 0; [self _enqueueComponentViewWithComponentHandle:componentHandle componentViewDescriptor:componentViewDescriptor]; diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 362513ebfda..f5e8317846b 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -19,22 +19,35 @@ #import "RCTComponentViewProtocol.h" #import "RCTComponentViewRegistry.h" #import "RCTConversions.h" +#import "RCTMountingTransactionObserverCoordinator.h" using namespace facebook; using namespace facebook::react; // `Create` instruction -static void RNCreateMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) +static void RNCreateMountInstruction( + ShadowViewMutation const &mutation, + RCTComponentViewRegistry *registry, + RCTMountingTransactionObserverCoordinator &observerCoordinator, + SurfaceId surfaceId) { - [registry dequeueComponentViewWithComponentHandle:mutation.newChildShadowView.componentHandle - tag:mutation.newChildShadowView.tag]; + auto componentViewDescriptor = + [registry dequeueComponentViewWithComponentHandle:mutation.newChildShadowView.componentHandle + tag:mutation.newChildShadowView.tag]; + + observerCoordinator.registerViewComponentDescriptor(componentViewDescriptor, surfaceId); } // `Delete` instruction -static void RNDeleteMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) +static void RNDeleteMountInstruction( + ShadowViewMutation const &mutation, + RCTComponentViewRegistry *registry, + RCTMountingTransactionObserverCoordinator &observerCoordinator, + SurfaceId surfaceId) { auto const &oldChildShadowView = mutation.oldChildShadowView; RCTComponentViewDescriptor componentViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag]; + observerCoordinator.unregisterViewComponentDescriptor(componentViewDescriptor, surfaceId); [registry enqueueComponentViewWithComponentHandle:oldChildShadowView.componentHandle tag:oldChildShadowView.tag componentViewDescriptor:componentViewDescriptor]; @@ -123,18 +136,22 @@ static void RNFinalizeUpdatesMountInstruction( } // `Update` instruction -static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, RCTComponentViewRegistry *registry) +static void RNPerformMountInstructions( + ShadowViewMutationList const &mutations, + RCTComponentViewRegistry *registry, + RCTMountingTransactionObserverCoordinator &observerCoordinator, + SurfaceId surfaceId) { SystraceSection s("RNPerformMountInstructions"); for (auto const &mutation : mutations) { switch (mutation.type) { case ShadowViewMutation::Create: { - RNCreateMountInstruction(mutation, registry); + RNCreateMountInstruction(mutation, registry, observerCoordinator, surfaceId); break; } case ShadowViewMutation::Delete: { - RNDeleteMountInstruction(mutation, registry); + RNDeleteMountInstruction(mutation, registry, observerCoordinator, surfaceId); break; } case ShadowViewMutation::Insert: { @@ -188,7 +205,9 @@ static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, } } -@implementation RCTMountingManager +@implementation RCTMountingManager { + RCTMountingTransactionObserverCoordinator _observerCoordinator; +} - (instancetype)init { @@ -250,8 +269,12 @@ static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, } RCTAssertMainQueue(); + auto metadata = MountingTransactionMetadata{surfaceId, transaction->getNumber(), transaction->getTelemetry()}; + [self.delegate mountingManager:self willMountComponentsWithRootTag:surfaceId]; - RNPerformMountInstructions(mutations, self.componentViewRegistry); + _observerCoordinator.notifyObserversMountingTransactionWillMount(metadata); + RNPerformMountInstructions(mutations, self.componentViewRegistry, _observerCoordinator, surfaceId); + _observerCoordinator.notifyObserversMountingTransactionDidMount(metadata); [self.delegate mountingManager:self didMountComponentsWithRootTag:surfaceId]; } diff --git a/React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.h b/React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.h new file mode 100644 index 00000000000..8c6bdefd843 --- /dev/null +++ b/React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import "RCTMountingTransactionObserverCoordinator.h" + +#import +#import + +#import + +class RCTMountingTransactionObserverCoordinator final { + public: + /* + * Registers (and unregisters) specified `componentViewDescriptor` in the registry of views that need to be notified. + * Does nothing if a particular `componentViewDescriptor` does not listen the events. + */ + void registerViewComponentDescriptor( + RCTComponentViewDescriptor const &componentViewDescriptor, + facebook::react::SurfaceId surfaceId); + void unregisterViewComponentDescriptor( + RCTComponentViewDescriptor const &componentViewDescriptor, + facebook::react::SurfaceId surfaceId); + + /* + * To be called from `RCTMountingManager`. + */ + void notifyObserversMountingTransactionWillMount( + facebook::react::MountingTransactionMetadata const &metadata) const; + void notifyObserversMountingTransactionDidMount( + facebook::react::MountingTransactionMetadata const &metadata) const; + + private: + facebook::better::map< + facebook::react::SurfaceId, + facebook::better::set> + registry_; +}; diff --git a/React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.mm b/React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.mm new file mode 100644 index 00000000000..405818ce92b --- /dev/null +++ b/React/Fabric/Mounting/RCTMountingTransactionObserverCoordinator.mm @@ -0,0 +1,72 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTMountingTransactionObserverCoordinator.h" + +#import "RCTMountingTransactionObserving.h" + +using namespace facebook::react; + +void RCTMountingTransactionObserverCoordinator::registerViewComponentDescriptor( + RCTComponentViewDescriptor const &componentViewDescriptor, + SurfaceId surfaceId) +{ + if (!componentViewDescriptor.observesMountingTransactionWillMount && + !componentViewDescriptor.observesMountingTransactionDidMount) { + return; + } + + auto &surfaceRegistry = registry_[surfaceId]; + surfaceRegistry.insert(componentViewDescriptor); +} + +void RCTMountingTransactionObserverCoordinator::unregisterViewComponentDescriptor( + RCTComponentViewDescriptor const &componentViewDescriptor, + SurfaceId surfaceId) +{ + if (!componentViewDescriptor.observesMountingTransactionWillMount && + !componentViewDescriptor.observesMountingTransactionDidMount) { + return; + } + + auto &surfaceRegistry = registry_[surfaceId]; + surfaceRegistry.erase(componentViewDescriptor); +} + +void RCTMountingTransactionObserverCoordinator::notifyObserversMountingTransactionWillMount( + MountingTransactionMetadata const &metadata) const +{ + auto surfaceId = metadata.surfaceId; + auto surfaceRegistryIterator = registry_.find(surfaceId); + if (surfaceRegistryIterator == registry_.end()) { + return; + } + auto &surfaceRegistry = surfaceRegistryIterator->second; + for (auto const &componentViewDescriptor : surfaceRegistry) { + if (componentViewDescriptor.observesMountingTransactionWillMount) { + [(id)componentViewDescriptor.view + mountingTransactionWillMountWithMetadata:metadata]; + } + } +} + +void RCTMountingTransactionObserverCoordinator::notifyObserversMountingTransactionDidMount( + MountingTransactionMetadata const &metadata) const +{ + auto surfaceId = metadata.surfaceId; + auto surfaceRegistryIterator = registry_.find(surfaceId); + if (surfaceRegistryIterator == registry_.end()) { + return; + } + auto &surfaceRegistry = surfaceRegistryIterator->second; + for (auto const &componentViewDescriptor : surfaceRegistry) { + if (componentViewDescriptor.observesMountingTransactionDidMount) { + [(id)componentViewDescriptor.view + mountingTransactionDidMountWithMetadata:metadata]; + } + } +} diff --git a/ReactCommon/fabric/mounting/MountingTransactionMetadata.h b/ReactCommon/fabric/mounting/MountingTransactionMetadata.h index 8858deaf00f..fe51700b4ae 100644 --- a/ReactCommon/fabric/mounting/MountingTransactionMetadata.h +++ b/ReactCommon/fabric/mounting/MountingTransactionMetadata.h @@ -16,7 +16,7 @@ namespace react { /* * Contains all (meta)information related to a MountingTransaction except a list * of mutation instructions. - * The class is meant to be used when a cosumer should not have access to all + * The class is meant to be used when a consumer should not have access to all * information about the transaction (incapsulation) but still needs to observe * it to produce some side-effects. */