diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h index c6579438f..a6c4b0b2f 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h @@ -11,6 +11,7 @@ #import #import #import +#import #import "RCTValueAnimatedNode.h" diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 67a0e844b..34a7a7031 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -28,7 +28,7 @@ RCT_EXPORT_MODULE(); { [_nodesManager stopAnimationLoop]; [self.bridge.eventDispatcher removeDispatchObserver:self]; - [self.bridge.uiManager removeUIManagerObserver:self]; + [self.bridge.uiManager.observerCoordinator removeObserver:self]; } - (dispatch_queue_t)methodQueue @@ -48,7 +48,7 @@ RCT_EXPORT_MODULE(); _preOperations = [NSMutableArray new]; [bridge.eventDispatcher addDispatchObserver:self]; - [bridge.uiManager addUIManagerObserver:self]; + [bridge.uiManager.observerCoordinator addObserver:self]; } #pragma mark -- API @@ -196,6 +196,8 @@ RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag [_preOperations addObject:operation]; } +#pragma mark - RCTUIManagerObserver + - (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)uiManager { if (_preOperations.count == 0 && _operations.count == 0) { diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index 2079082ba..ae59a2da5 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -59,24 +59,7 @@ RCT_EXTERN NSString *const RCTUIManagerDidRemoveRootViewNotification; */ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; -@class RCTUIManager; - -/** - * Allows to hook into UIManager internals. This can be used to execute code at - * specific points during the view updating process. - */ -@protocol RCTUIManagerObserver - -/** - * Called before flushing UI blocks at the end of a batch. Note that this won't - * get called for partial batches when using `unsafeFlushUIChangesBeforeBatchEnds`. - * This is called from the UIManager queue. Can be used to add UI operations in that batch. - */ -- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)manager; - -@end - -@protocol RCTScrollableProtocol; +@class RCTUIManagerObserverCoordinator; /** * The RCTUIManager is the module responsible for updating the view hierarchy. @@ -139,17 +122,6 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ - (void)prependUIBlock:(RCTViewManagerUIBlock)block; -/** - * Add a UIManagerObserver. See the RCTUIManagerObserver protocol for more info. This - * method can be called safely from any queue. - */ -- (void)addUIManagerObserver:(id)observer; - -/** - * Remove a UIManagerObserver. This method can be called safely from any queue. - */ -- (void)removeUIManagerObserver:(id)observer; - /** * Used by native animated module to bypass the process of updating the values through the shadow * view hierarchy. This method will directly update native views, which means that updates for @@ -192,6 +164,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ - (void)setNeedsLayout; +/** + * Dedicated object for subscribing for UIManager events. + * See `RCTUIManagerObserver` protocol for more details. + */ +@property (atomic, retain, readonly) RCTUIManagerObserverCoordinator *observerCoordinator; + @end @interface RCTUIManager (Deprecated) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 56e54e474..2147b4eaf 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -33,6 +33,7 @@ #import "RCTScrollableProtocol.h" #import "RCTShadowView.h" #import "RCTUtils.h" +#import "RCTUIManagerObserverCoordinator.h" #import "RCTView.h" #import "RCTViewManager.h" #import "UIView+React.h" @@ -225,7 +226,6 @@ static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnim NSDictionary *_componentDataByName; NSMutableSet> *_bridgeTransactionListeners; - NSMutableSet> *_uiManagerObservers; } @synthesize bridge = _bridge; @@ -306,7 +306,7 @@ RCT_EXPORT_MODULE() _rootViewTags = [NSMutableSet new]; _bridgeTransactionListeners = [NSMutableSet new]; - _uiManagerObservers = [NSMutableSet new]; + _observerCoordinator = [RCTUIManagerObserverCoordinator new]; _viewsToBeDeleted = [NSMutableSet new]; @@ -723,20 +723,6 @@ BOOL RCTIsUIManagerQueue() } } -- (void)addUIManagerObserver:(id)observer -{ - dispatch_async(RCTGetUIManagerQueue(), ^{ - [self->_uiManagerObservers addObject:observer]; - }); -} - -- (void)removeUIManagerObserver:(id)observer -{ - dispatch_async(RCTGetUIManagerQueue(), ^{ - [self->_uiManagerObservers removeObject:observer]; - }); -} - /** * A method to be called from JS, which takes a container ID and then releases * all subviews for that container upon receipt. @@ -1159,10 +1145,19 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag [self addUIBlock:uiBlock]; } + [_observerCoordinator uiManagerWillPerformLayout:self]; + // Perform layout for (NSNumber *reactTag in _rootViewTags) { RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag]; [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]]; + } + + [_observerCoordinator uiManagerDidPerformLayout:self]; + + // Properies propagation + for (NSNumber *reactTag in _rootViewTags) { + RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag]; [self _amendPendingUIBlocksWithStylePropagationUpdateForShadowView:rootView]; } @@ -1175,9 +1170,7 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag } }]; - for (id observer in _uiManagerObservers) { - [observer uiManagerWillFlushUIBlocks:self]; - } + [_observerCoordinator uiManagerWillFlushUIBlocks:self]; [self flushUIBlocks]; } diff --git a/React/Modules/RCTUIManagerObserverCoordinator.h b/React/Modules/RCTUIManagerObserverCoordinator.h new file mode 100644 index 000000000..c39cea2ec --- /dev/null +++ b/React/Modules/RCTUIManagerObserverCoordinator.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2015-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 + +#import + +/** + * Allows to hook into UIManager internals. This can be used to execute code at + * specific points during the view updating process. + * All observer handler is called on UIManager queue. + */ +@protocol RCTUIManagerObserver + +@optional + +/** + * Called just before the UIManager layout views. + * It allows performing some operation for components which contain custom + * layout logic right before regular Yoga based layout. So, for instance, + * some components which have own React-independent state can compute and cache + * own intrinsic content size (which will be used by Yoga) at this point. + */ +- (void)uiManagerWillPerformLayout:(RCTUIManager *)manager; + +/** + * Called just after the UIManager layout views. + * It allows performing custom layout logic right after regular Yoga based layout. + * So, for instance, this can be used for computing final layout for a component, + * since it has its final frame set by Yoga at this point. + */ +- (void)uiManagerDidPerformLayout:(RCTUIManager *)manager; + +/** + * Called before flushing UI blocks at the end of a batch. Note that this won't + * get called for partial batches when using `unsafeFlushUIChangesBeforeBatchEnds`. + * This is called from the UIManager queue. Can be used to add UI operations in that batch. + */ +- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)manager; + +@end + +/** + * Simple helper which take care of RCTUIManager's observers. + */ +@interface RCTUIManagerObserverCoordinator : NSObject + +/** + * Add a UIManagerObserver. See the `RCTUIManagerObserver` protocol for more info. + * References to observers are held weakly. + * This method can be called safely from any queue. + */ +- (void)addObserver:(id)observer; + +/** + * Remove a `UIManagerObserver`. + * This method can be called safely from any queue. + */ +- (void)removeObserver:(id)observer; + +@end diff --git a/React/Modules/RCTUIManagerObserverCoordinator.m b/React/Modules/RCTUIManagerObserverCoordinator.m new file mode 100644 index 000000000..f16acaa4e --- /dev/null +++ b/React/Modules/RCTUIManagerObserverCoordinator.m @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015-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 "RCTUIManagerObserverCoordinator.h" + +#import "RCTUIManager.h" + +@implementation RCTUIManagerObserverCoordinator { + NSHashTable> *_observers; +} + +- (instancetype)init +{ + if (self = [super init]) { + _observers = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:0]; + } + + return self; +} + +- (void)addObserver:(id)observer +{ + dispatch_async(RCTGetUIManagerQueue(), ^{ + [self->_observers addObject:observer]; + }); +} + +- (void)removeObserver:(id)observer +{ + dispatch_async(RCTGetUIManagerQueue(), ^{ + [self->_observers removeObject:observer]; + }); +} + +#pragma mark - RCTUIManagerObserver + +- (void)uiManagerWillPerformLayout:(RCTUIManager *)manager +{ + for (id observer in _observers) { + if ([observer respondsToSelector:@selector(uiManagerWillPerformLayout:)]) { + [observer uiManagerWillPerformLayout:manager]; + } + } +} + +- (void)uiManagerDidPerformLayout:(RCTUIManager *)manager +{ + for (id observer in _observers) { + if ([observer respondsToSelector:@selector(uiManagerDidPerformLayout:)]) { + [observer uiManagerDidPerformLayout:manager]; + } + } +} + +- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)manager +{ + for (id observer in _observers) { + if ([observer respondsToSelector:@selector(uiManagerWillFlushUIBlocks:)]) { + [observer uiManagerWillFlushUIBlocks:manager]; + } + } +} + +@end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 155605541..29ddbfc9c 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -951,6 +951,12 @@ 59A7B9FE1E577DBF0068EDBF /* RCTRootContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A7B9FC1E577DBF0068EDBF /* RCTRootContentView.m */; }; 59B1EBC91EBD46250047B19B /* RCTShadowView+Layout.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */; }; 59B1EBCA1EBD47520047B19B /* RCTShadowView+Layout.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */; }; + 59EB6DBB1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */; }; + 59EB6DBC1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */; }; + 59EB6DBD1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB6DBA1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m */; }; + 59EB6DBE1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB6DBA1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m */; }; + 59EB6DBF1EBD6FFC0072A5E7 /* RCTUIManagerObserverCoordinator.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */; }; + 59EB6DC01EBD70130072A5E7 /* RCTUIManagerObserverCoordinator.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */; }; 59FBEFB01E46D91C0095D885 /* RCTScrollContentShadowView.h in Headers */ = {isa = PBXBuildFile; fileRef = 59FBEFAC1E46D91C0095D885 /* RCTScrollContentShadowView.h */; }; 59FBEFB11E46D91C0095D885 /* RCTScrollContentShadowView.h in Headers */ = {isa = PBXBuildFile; fileRef = 59FBEFAC1E46D91C0095D885 /* RCTScrollContentShadowView.h */; }; 59FBEFB21E46D91C0095D885 /* RCTScrollContentShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59FBEFAD1E46D91C0095D885 /* RCTScrollContentShadowView.m */; }; @@ -1078,6 +1084,7 @@ dstPath = include/React; dstSubfolderSpec = 16; files = ( + 59EB6DC01EBD70130072A5E7 /* RCTUIManagerObserverCoordinator.h in Copy Headers */, 59B1EBCA1EBD47520047B19B /* RCTShadowView+Layout.h in Copy Headers */, 3D7BFD351EA8E43F008DFB7A /* RCTDevSettings.h in Copy Headers */, 3D7BFD331EA8E433008DFB7A /* RCTPackagerClient.h in Copy Headers */, @@ -1271,6 +1278,7 @@ dstPath = include/React; dstSubfolderSpec = 16; files = ( + 59EB6DBF1EBD6FFC0072A5E7 /* RCTUIManagerObserverCoordinator.h in Copy Headers */, 59B1EBC91EBD46250047B19B /* RCTShadowView+Layout.h in Copy Headers */, 3D7BFD311EA8E41F008DFB7A /* RCTPackagerClient.h in Copy Headers */, 3D7BFD291EA8E37B008DFB7A /* RCTDevSettings.h in Copy Headers */, @@ -1799,6 +1807,8 @@ 590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTShadowView+Layout.m"; sourceTree = ""; }; 59A7B9FB1E577DBF0068EDBF /* RCTRootContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootContentView.h; sourceTree = ""; }; 59A7B9FC1E577DBF0068EDBF /* RCTRootContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootContentView.m; sourceTree = ""; }; + 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManagerObserverCoordinator.h; sourceTree = ""; }; + 59EB6DBA1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerObserverCoordinator.m; sourceTree = ""; }; 59FBEFAC1E46D91C0095D885 /* RCTScrollContentShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollContentShadowView.h; sourceTree = ""; }; 59FBEFAD1E46D91C0095D885 /* RCTScrollContentShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollContentShadowView.m; sourceTree = ""; }; 59FBEFAE1E46D91C0095D885 /* RCTScrollContentViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollContentViewManager.h; sourceTree = ""; }; @@ -2042,12 +2052,6 @@ 13B07FE01A69315300A75B9A /* Modules */ = { isa = PBXGroup; children = ( - CF2731BE1E7B8DE40044CA4F /* RCTDeviceInfo.h */, - CF2731BF1E7B8DE40044CA4F /* RCTDeviceInfo.m */, - 130E3D861E6A082100ACE484 /* RCTDevSettings.h */, - 130E3D871E6A082100ACE484 /* RCTDevSettings.mm */, - 369123DF1DDC75850095B341 /* RCTJSCSamplingProfiler.h */, - 369123E01DDC75850095B341 /* RCTJSCSamplingProfiler.m */, E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */, E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */, 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */, @@ -2058,6 +2062,10 @@ 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */, 13D033611C1837FE0021DC29 /* RCTClipboard.h */, 13D033621C1837FE0021DC29 /* RCTClipboard.m */, + CF2731BE1E7B8DE40044CA4F /* RCTDeviceInfo.h */, + CF2731BF1E7B8DE40044CA4F /* RCTDeviceInfo.m */, + 130E3D861E6A082100ACE484 /* RCTDevSettings.h */, + 130E3D871E6A082100ACE484 /* RCTDevSettings.mm */, 13D9FEE91CDCCECF00158BD7 /* RCTEventEmitter.h */, 13D9FEEA1CDCCECF00158BD7 /* RCTEventEmitter.m */, 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */, @@ -2066,6 +2074,8 @@ B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */, 352DCFEE1D19F4C20056D623 /* RCTI18nUtil.h */, 352DCFEF1D19F4C20056D623 /* RCTI18nUtil.m */, + 369123DF1DDC75850095B341 /* RCTJSCSamplingProfiler.h */, + 369123E01DDC75850095B341 /* RCTJSCSamplingProfiler.m */, 13D9FEEC1CDCD93000158BD7 /* RCTKeyboardObserver.h */, 13D9FEED1CDCD93000158BD7 /* RCTKeyboardObserver.m */, 13F17A831B8493E5007D4C75 /* RCTRedBox.h */, @@ -2078,6 +2088,8 @@ 13B07FEE1A69327A00A75B9A /* RCTTiming.m */, 13E067481A70F434002CDEE1 /* RCTUIManager.h */, 13E067491A70F434002CDEE1 /* RCTUIManager.m */, + 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */, + 59EB6DBA1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m */, ); path = Modules; sourceTree = ""; @@ -2627,6 +2639,7 @@ 3D302F811DF828F800D6DDAE /* RCTNavItemManager.h in Headers */, 135A9C061E7B0F7800587AEB /* RCTJSCHelpers.h in Headers */, 3D302F841DF828F800D6DDAE /* RCTPointerEvents.h in Headers */, + 59EB6DBC1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h in Headers */, 3D302F851DF828F800D6DDAE /* RCTProgressViewManager.h in Headers */, 3D302F861DF828F800D6DDAE /* RCTRefreshControl.h in Headers */, 3D302F871DF828F800D6DDAE /* RCTRefreshControlManager.h in Headers */, @@ -2834,6 +2847,7 @@ 3D80DA401DF820620028D040 /* RCTTouchEvent.h in Headers */, 3D80DA411DF820620028D040 /* RCTTouchHandler.h in Headers */, 13134C8C1E296B2A00B9F3CB /* RCTMessageThread.h in Headers */, + 59EB6DBB1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h in Headers */, 3D80DA421DF820620028D040 /* RCTURLRequestDelegate.h in Headers */, 3D80DA431DF820620028D040 /* RCTURLRequestHandler.h in Headers */, 3D80DA441DF820620028D040 /* RCTUtils.h in Headers */, @@ -3305,6 +3319,7 @@ 2D3B5ECF1D9B096F00451313 /* RCTFont.mm in Sources */, 2D3B5ED51D9B098000451313 /* RCTModalHostViewController.m in Sources */, 2D3B5EBC1D9B092600451313 /* RCTKeyboardObserver.m in Sources */, + 59EB6DBE1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m in Sources */, 2D3B5E971D9B089000451313 /* RCTBridge.m in Sources */, 2D3B5EA21D9B08BA00451313 /* RCTModuleMethod.m in Sources */, 2D3B5E9B1D9B08A000451313 /* RCTFrameUpdate.m in Sources */, @@ -3514,6 +3529,7 @@ 006FC4141D9B20820057AAAD /* RCTMultipartDataTask.m in Sources */, 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */, 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, + 59EB6DBD1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.m in Sources */, 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */, 130443A21E3FEAA900D93A67 /* RCTFollyConvert.mm in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */,