123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- /*
- * 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 "RCTSurface.h"
- #import "RCTSurfaceView+Internal.h"
- #import <mutex>
- #import <stdatomic.h>
- #import "RCTAssert.h"
- #import "RCTBridge+Private.h"
- #import "RCTBridge.h"
- #import "RCTShadowView+Layout.h"
- #import "RCTSurfaceDelegate.h"
- #import "RCTSurfaceRootShadowView.h"
- #import "RCTSurfaceRootShadowViewDelegate.h"
- #import "RCTSurfaceRootView.h"
- #import "RCTSurfaceView.h"
- #import "RCTTouchHandler.h"
- #import "RCTUIManager.h"
- #import "RCTUIManagerObserverCoordinator.h"
- #import "RCTUIManagerUtils.h"
- @interface RCTSurface () <RCTSurfaceRootShadowViewDelegate, RCTUIManagerObserver>
- @end
- @implementation RCTSurface {
- // Immutable
- RCTBridge *_bridge;
- NSString *_moduleName;
- NSNumber *_rootViewTag;
- // Protected by the `_mutex`
- std::mutex _mutex;
- RCTBridge *_batchedBridge;
- RCTSurfaceStage _stage;
- NSDictionary *_properties;
- CGSize _minimumSize;
- CGSize _maximumSize;
- CGSize _intrinsicSize;
- RCTUIManagerMountingBlock _mountingBlock;
- // The Main thread only
- RCTSurfaceView *_Nullable _view;
- RCTTouchHandler *_Nullable _touchHandler;
- // Semaphores
- dispatch_semaphore_t _rootShadowViewDidStartRenderingSemaphore;
- dispatch_semaphore_t _rootShadowViewDidStartLayingOutSemaphore;
- dispatch_semaphore_t _uiManagerDidPerformMountingSemaphore;
- // Atomics
- atomic_bool _waitingForMountingStageOnMainQueue;
- }
- - (instancetype)initWithBridge:(RCTBridge *)bridge
- moduleName:(NSString *)moduleName
- initialProperties:(NSDictionary *)initialProperties
- {
- RCTAssert(bridge.valid, @"Valid bridge is required to instantiate `RCTSurface`.");
- if (self = [super init]) {
- _bridge = bridge;
- _batchedBridge = [_bridge batchedBridge] ?: _bridge;
- _moduleName = moduleName;
- _properties = [initialProperties copy];
- _rootViewTag = RCTAllocateRootViewTag();
- _rootShadowViewDidStartRenderingSemaphore = dispatch_semaphore_create(0);
- _rootShadowViewDidStartLayingOutSemaphore = dispatch_semaphore_create(0);
- _uiManagerDidPerformMountingSemaphore = dispatch_semaphore_create(0);
- _minimumSize = CGSizeZero;
- _maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(handleBridgeWillLoadJavaScriptNotification:)
- name:RCTJavaScriptWillStartLoadingNotification
- object:_bridge];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(handleBridgeDidLoadJavaScriptNotification:)
- name:RCTJavaScriptDidLoadNotification
- object:_bridge];
- _stage = RCTSurfaceStageSurfaceDidInitialize;
- if (!bridge.loading) {
- _stage = _stage | RCTSurfaceStageBridgeDidLoad;
- }
- [_bridge.uiManager.observerCoordinator addObserver:self];
- [self _registerRootView];
- [self _run];
- }
- return self;
- }
- - (void)dealloc
- {
- [self _stop];
- }
- #pragma mark - Immutable Properties (no need to enforce synchronization)
- - (RCTBridge *)bridge
- {
- return _bridge;
- }
- - (NSString *)moduleName
- {
- return _moduleName;
- }
- - (NSNumber *)rootViewTag
- {
- return _rootViewTag;
- }
- #pragma mark - Convenience Internal Thread-Safe Properties
- - (RCTBridge *)_batchedBridge
- {
- std::lock_guard<std::mutex> lock(_mutex);
- return _batchedBridge;
- }
- - (RCTUIManager *)_uiManager
- {
- return self._batchedBridge.uiManager;
- }
- #pragma mark - Main-Threaded Routines
- - (RCTSurfaceView *)view
- {
- RCTAssertMainQueue();
- if (!_view) {
- _view = [[RCTSurfaceView alloc] initWithSurface:self];
- _touchHandler = [[RCTTouchHandler alloc] initWithBridge:self.bridge];
- [_touchHandler attachToView:_view];
- [self _mountRootViewIfNeeded];
- }
- return _view;
- }
- - (void)_mountRootViewIfNeeded
- {
- RCTAssertMainQueue();
- RCTSurfaceView *view = self->_view;
- if (!view) {
- return;
- }
- RCTSurfaceRootView *rootView = (RCTSurfaceRootView *)[self._uiManager viewForReactTag:self->_rootViewTag];
- if (!rootView) {
- return;
- }
- RCTAssert(
- [rootView isKindOfClass:[RCTSurfaceRootView class]],
- @"Received root view is not an instance of `RCTSurfaceRootView`.");
- if (rootView.superview != view) {
- view.rootView = rootView;
- }
- }
- #pragma mark - Bridge Events
- - (void)handleBridgeWillLoadJavaScriptNotification:(__unused NSNotification *)notification
- {
- RCTAssertMainQueue();
- // Reset states because the bridge is reloading. This is similar to initialization phase.
- _stage = RCTSurfaceStageSurfaceDidInitialize;
- _view = nil;
- _touchHandler = nil;
- [self _setStage:RCTSurfaceStageBridgeDidLoad];
- }
- - (void)handleBridgeDidLoadJavaScriptNotification:(NSNotification *)notification
- {
- RCTAssertMainQueue();
- [self _setStage:RCTSurfaceStageModuleDidLoad];
- RCTBridge *bridge = notification.userInfo[@"bridge"];
- BOOL isRerunNeeded = NO;
- {
- std::lock_guard<std::mutex> lock(_mutex);
- if (bridge != _batchedBridge) {
- _batchedBridge = bridge;
- isRerunNeeded = YES;
- }
- }
- if (isRerunNeeded) {
- [self _registerRootView];
- [self _run];
- }
- }
- #pragma mark - Stage management
- - (RCTSurfaceStage)stage
- {
- std::lock_guard<std::mutex> lock(_mutex);
- return _stage;
- }
- - (void)_setStage:(RCTSurfaceStage)stage
- {
- RCTSurfaceStage updatedStage;
- {
- std::lock_guard<std::mutex> lock(_mutex);
- if (_stage & stage) {
- return;
- }
- updatedStage = (RCTSurfaceStage)(_stage | stage);
- _stage = updatedStage;
- }
- [self _propagateStageChange:updatedStage];
- }
- - (void)_propagateStageChange:(RCTSurfaceStage)stage
- {
- // Updating the `view`
- RCTExecuteOnMainQueue(^{
- self->_view.stage = stage;
- });
- // Notifying the `delegate`
- id<RCTSurfaceDelegate> delegate = self.delegate;
- if ([delegate respondsToSelector:@selector(surface:didChangeStage:)]) {
- [delegate surface:self didChangeStage:stage];
- }
- }
- #pragma mark - Properties Management
- - (NSDictionary *)properties
- {
- std::lock_guard<std::mutex> lock(_mutex);
- return _properties;
- }
- - (void)setProperties:(NSDictionary *)properties
- {
- {
- std::lock_guard<std::mutex> lock(_mutex);
- if ([properties isEqualToDictionary:_properties]) {
- return;
- }
- _properties = [properties copy];
- }
- [self _run];
- }
- #pragma mark - Running
- - (void)_run
- {
- RCTBridge *batchedBridge;
- NSDictionary *properties;
- {
- std::lock_guard<std::mutex> lock(_mutex);
- batchedBridge = _batchedBridge;
- properties = _properties;
- }
- if (!batchedBridge.valid) {
- return;
- }
- NSDictionary *applicationParameters = @{
- @"rootTag" : _rootViewTag,
- @"initialProps" : properties,
- };
- RCTLogInfo(@"Running surface %@ (%@)", _moduleName, applicationParameters);
- [self mountReactComponentWithBridge:batchedBridge moduleName:_moduleName params:applicationParameters];
- [self _setStage:RCTSurfaceStageSurfaceDidRun];
- }
- - (void)_stop
- {
- [self unmountReactComponentWithBridge:self._batchedBridge rootViewTag:self->_rootViewTag];
- }
- - (void)_registerRootView
- {
- RCTBridge *batchedBridge;
- CGSize minimumSize;
- CGSize maximumSize;
- {
- std::lock_guard<std::mutex> lock(_mutex);
- batchedBridge = _batchedBridge;
- minimumSize = _minimumSize;
- maximumSize = _maximumSize;
- }
- RCTUIManager *uiManager = batchedBridge.uiManager;
- // If we are on the main queue now, we have to proceed synchronously.
- // Otherwise, we cannot perform synchronous waiting for some stages later.
- (RCTIsMainQueue() ? RCTUnsafeExecuteOnUIManagerQueueSync : RCTExecuteOnUIManagerQueue)(^{
- [uiManager registerRootViewTag:self->_rootViewTag];
- RCTSurfaceRootShadowView *rootShadowView =
- (RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag];
- RCTAssert(
- [rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]],
- @"Received shadow view is not an instance of `RCTSurfaceRootShadowView`.");
- [rootShadowView setMinimumSize:minimumSize maximumSize:maximumSize];
- rootShadowView.delegate = self;
- });
- }
- #pragma mark - Layout
- - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
- {
- RCTUIManager *uiManager = self._uiManager;
- __block CGSize fittingSize;
- RCTUnsafeExecuteOnUIManagerQueueSync(^{
- RCTSurfaceRootShadowView *rootShadowView =
- (RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag];
- RCTAssert(
- [rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]],
- @"Received shadow view is not an instance of `RCTSurfaceRootShadowView`.");
- fittingSize = [rootShadowView sizeThatFitsMinimumSize:minimumSize maximumSize:maximumSize];
- });
- return fittingSize;
- }
- #pragma mark - Size Constraints
- - (void)setSize:(CGSize)size
- {
- [self setMinimumSize:size maximumSize:size];
- }
- - (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
- {
- {
- std::lock_guard<std::mutex> lock(_mutex);
- if (CGSizeEqualToSize(minimumSize, _minimumSize) && CGSizeEqualToSize(maximumSize, _maximumSize)) {
- return;
- }
- _maximumSize = maximumSize;
- _minimumSize = minimumSize;
- }
- RCTUIManager *uiManager = self._uiManager;
- RCTUnsafeExecuteOnUIManagerQueueSync(^{
- RCTSurfaceRootShadowView *rootShadowView =
- (RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag];
- RCTAssert(
- [rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]],
- @"Received shadow view is not an instance of `RCTSurfaceRootShadowView`.");
- [rootShadowView setMinimumSize:minimumSize maximumSize:maximumSize];
- [uiManager setNeedsLayout];
- });
- }
- - (CGSize)minimumSize
- {
- std::lock_guard<std::mutex> lock(_mutex);
- return _minimumSize;
- }
- - (CGSize)maximumSize
- {
- std::lock_guard<std::mutex> lock(_mutex);
- return _maximumSize;
- }
- #pragma mark - intrinsicSize
- - (void)setIntrinsicSize:(CGSize)intrinsicSize
- {
- {
- std::lock_guard<std::mutex> lock(_mutex);
- if (CGSizeEqualToSize(intrinsicSize, _intrinsicSize)) {
- return;
- }
- _intrinsicSize = intrinsicSize;
- }
- // Notifying `delegate`
- id<RCTSurfaceDelegate> delegate = self.delegate;
- if ([delegate respondsToSelector:@selector(surface:didChangeIntrinsicSize:)]) {
- [delegate surface:self didChangeIntrinsicSize:intrinsicSize];
- }
- }
- - (CGSize)intrinsicSize
- {
- std::lock_guard<std::mutex> lock(_mutex);
- return _intrinsicSize;
- }
- #pragma mark - Synchronous Waiting
- - (BOOL)synchronouslyWaitForStage:(RCTSurfaceStage)stage timeout:(NSTimeInterval)timeout
- {
- if (RCTIsUIManagerQueue()) {
- RCTLogInfo(@"Synchronous waiting is not supported on UIManager queue.");
- return NO;
- }
- if (RCTIsMainQueue() && (stage & RCTSurfaceStageSurfaceDidInitialMounting)) {
- // All main-threaded execution (especially mounting process) has to be
- // intercepted, captured and performed synchronously at the end of this method
- // right after the semaphore signals.
- // Atomic variant of `_waitingForMountingStageOnMainQueue = YES;`
- atomic_fetch_or(&_waitingForMountingStageOnMainQueue, 1);
- }
- dispatch_semaphore_t semaphore;
- switch (stage) {
- case RCTSurfaceStageSurfaceDidInitialLayout:
- semaphore = _rootShadowViewDidStartLayingOutSemaphore;
- break;
- case RCTSurfaceStageSurfaceDidInitialRendering:
- semaphore = _rootShadowViewDidStartRenderingSemaphore;
- break;
- case RCTSurfaceStageSurfaceDidInitialMounting:
- semaphore = _uiManagerDidPerformMountingSemaphore;
- break;
- default:
- RCTAssert(
- NO,
- @"Only waiting for `RCTSurfaceStageSurfaceDidInitialRendering`, `RCTSurfaceStageSurfaceDidInitialLayout` and `RCTSurfaceStageSurfaceDidInitialMounting` stages are supported.");
- }
- BOOL timeoutOccurred = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC));
- // Atomic equivalent of `_waitingForMountingStageOnMainQueue = NO;`.
- atomic_fetch_and(&_waitingForMountingStageOnMainQueue, 0);
- if (!timeoutOccurred) {
- // Balancing the semaphore.
- // Note: `dispatch_semaphore_wait` reverts the decrement in case when timeout occurred.
- dispatch_semaphore_signal(semaphore);
- }
- if (RCTIsMainQueue() && (stage & RCTSurfaceStageSurfaceDidInitialMounting)) {
- // Time to apply captured mounting block.
- RCTUIManagerMountingBlock mountingBlock;
- {
- std::lock_guard<std::mutex> lock(_mutex);
- mountingBlock = _mountingBlock;
- _mountingBlock = nil;
- }
- if (mountingBlock) {
- mountingBlock();
- [self _mountRootViewIfNeeded];
- }
- }
- return !timeoutOccurred;
- }
- #pragma mark - RCTSurfaceRootShadowViewDelegate
- - (void)rootShadowView:(__unused RCTRootShadowView *)rootShadowView didChangeIntrinsicSize:(CGSize)intrinsicSize
- {
- self.intrinsicSize = intrinsicSize;
- }
- - (void)rootShadowViewDidStartRendering:(__unused RCTSurfaceRootShadowView *)rootShadowView
- {
- [self _setStage:RCTSurfaceStageSurfaceDidInitialRendering];
- dispatch_semaphore_signal(_rootShadowViewDidStartRenderingSemaphore);
- }
- - (void)rootShadowViewDidStartLayingOut:(__unused RCTSurfaceRootShadowView *)rootShadowView
- {
- [self _setStage:RCTSurfaceStageSurfaceDidInitialLayout];
- dispatch_semaphore_signal(_rootShadowViewDidStartLayingOutSemaphore);
- RCTExecuteOnMainQueue(^{
- // Rendering is happening, let's mount `rootView` into `view` if we already didn't do this.
- [self _mountRootViewIfNeeded];
- });
- }
- #pragma mark - RCTUIManagerObserver
- - (BOOL)uiManager:(__unused RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block
- {
- if (atomic_load(&_waitingForMountingStageOnMainQueue) && (self.stage & RCTSurfaceStageSurfaceDidInitialLayout)) {
- // Atomic equivalent of `_waitingForMountingStageOnMainQueue = NO;`.
- atomic_fetch_and(&_waitingForMountingStageOnMainQueue, 0);
- {
- std::lock_guard<std::mutex> lock(_mutex);
- _mountingBlock = block;
- }
- return YES;
- }
- return NO;
- }
- - (void)uiManagerDidPerformMounting:(__unused RCTUIManager *)manager
- {
- if (self.stage & RCTSurfaceStageSurfaceDidInitialLayout) {
- [self _setStage:RCTSurfaceStageSurfaceDidInitialMounting];
- dispatch_semaphore_signal(_uiManagerDidPerformMountingSemaphore);
- // No need to listen to UIManager anymore.
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
- [self->_bridge.uiManager.observerCoordinator removeObserver:self];
- });
- }
- }
- - (BOOL)start
- {
- // Does nothing.
- // The Start&Stop feature is not implemented for regular Surface yet.
- return YES;
- }
- - (BOOL)stop
- {
- // Does nothing.
- // The Start&Stop feature is not implemented for regular Surface yet.
- return YES;
- }
- #pragma mark - Mounting/Unmounting of React components
- - (void)mountReactComponentWithBridge:(RCTBridge *)bridge
- moduleName:(NSString *)moduleName
- params:(NSDictionary *)params
- {
- [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, params ] completion:NULL];
- }
- - (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag
- {
- [bridge enqueueJSCall:@"AppRegistry"
- method:@"unmountApplicationComponentAtRootTag"
- args:@[ rootViewTag ]
- completion:NULL];
- }
- @end
|