react-native/React/Fabric/RCTSurfacePresenter.mm
Kevin Gozali 7b9b1559a7 iOS: Avoid double reload event when reloading JS
Summary:
@public
The bridge may send the reload event twice when reloading JS in some rare condition. The 2nd one was actually when the "new" bridge wasn't valid yet, so log an error if that happens. This may happen if the connection to Metro server gets into a weird state. Restarting Metro would solve the problem.

The issue was that `RCTBridgeWillReloadNotification` fired twice for each reload with metro connected.

Reviewed By: mmmulani

Differential Revision: D8509448

fbshipit-source-id: 647fb98c0a97503409a10451b8f152145947ae20
2018-06-19 18:47:41 -07:00

208 lines
6.7 KiB
Plaintext

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSurfacePresenter.h"
#import <React/RCTAssert.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTComponentViewRegistry.h>
#import <React/RCTFabricSurface.h>
#import <React/RCTMountingManager.h>
#import <React/RCTMountingManagerDelegate.h>
#import <React/RCTScheduler.h>
#import <React/RCTSurfaceRegistry.h>
#import <React/RCTSurfaceView.h>
#import <React/RCTSurfaceView+Internal.h>
#import <fabric/core/LayoutContext.h>
#import <fabric/core/LayoutConstraints.h>
#import "RCTConversions.h"
using namespace facebook::react;
@interface RCTSurfacePresenter () <RCTSchedulerDelegate, RCTMountingManagerDelegate>
@end
@implementation RCTSurfacePresenter {
RCTScheduler *_scheduler;
RCTMountingManager *_mountingManager;
RCTBridge *_bridge;
RCTBridge *_batchedBridge;
RCTSurfaceRegistry *_surfaceRegistry;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_batchedBridge = [_bridge batchedBridge] ?: _bridge;
_scheduler = [[RCTScheduler alloc] init];
_scheduler.delegate = self;
_surfaceRegistry = [[RCTSurfaceRegistry alloc] init];
_mountingManager = [[RCTMountingManager alloc] init];
_mountingManager.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleBridgeWillReloadNotification:)
name:RCTBridgeWillReloadNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleJavaScriptDidLoadNotification:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)schedulerDidComputeMutationInstructions:(facebook::react::TreeMutationInstructionList)instructions rootTag:(ReactTag)rootTag
{
[_mountingManager mutateComponentViewTreeWithMutationInstructions:instructions
rootTag:rootTag];
}
- (void)schedulerDidRequestPreliminaryViewAllocationWithComponentName:(NSString *)componentName
{
// TODO: To be implemeted.
}
#pragma mark - Internal Surface-dedicated Interface
- (void)registerSurface:(RCTFabricSurface *)surface
{
[_surfaceRegistry registerSurface:surface];
[_scheduler registerRootTag:surface.rootViewTag.integerValue];
[self runSurface:surface];
// FIXME: Mutation instruction MUST produce instruction for root node.
[_mountingManager.componentViewRegistry dequeueComponentViewWithName:@"Root" tag:surface.rootViewTag.integerValue];
}
- (void)unregisterSurface:(RCTFabricSurface *)surface
{
[self stopSurface:surface];
[_scheduler unregisterRootTag:surface.rootViewTag.integerValue];
[_surfaceRegistry unregisterSurface:surface];
}
- (RCTFabricSurface *)surfaceForRootTag:(ReactTag)rootTag
{
return [_surfaceRegistry surfaceForRootTag:rootTag];
}
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize
maximumSize:(CGSize)maximumSize
surface:(RCTFabricSurface *)surface
{
LayoutContext layoutContext;
LayoutConstraints layoutConstraints = {};
layoutConstraints.minimumSize = RCTSizeFromCGSize(minimumSize);
layoutConstraints.maximumSize = RCTSizeFromCGSize(maximumSize);
return [_scheduler measureWithLayoutConstraints:layoutConstraints
layoutContext:layoutContext
rootTag:surface.rootTag];
}
- (void)setMinimumSize:(CGSize)minimumSize
maximumSize:(CGSize)maximumSize
surface:(RCTFabricSurface *)surface
{
LayoutContext layoutContext;
LayoutConstraints layoutConstraints = {};
layoutConstraints.minimumSize = RCTSizeFromCGSize(minimumSize);
layoutConstraints.maximumSize = RCTSizeFromCGSize(maximumSize);
[_scheduler constraintLayoutWithLayoutConstraints:layoutConstraints
layoutContext:layoutContext
rootTag:surface.rootTag];
}
- (void)runSurface:(RCTFabricSurface *)surface
{
NSDictionary *applicationParameters = @{
@"rootTag": surface.rootViewTag,
@"initialProps": surface.properties,
};
[_batchedBridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[surface.moduleName, applicationParameters] completion:NULL];
}
- (void)stopSurface:(RCTFabricSurface *)surface
{
[_batchedBridge enqueueJSCall:@"AppRegistry" method:@"unmountApplicationComponentAtRootTag" args:@[surface.rootViewTag] completion:NULL];
}
#pragma mark - RCTMountingManagerDelegate
- (void)mountingManager:(RCTMountingManager *)mountingManager willMountComponentsWithRootTag:(ReactTag)rootTag
{
RCTIsMainQueue();
// TODO: Propagate state change to Surface.
}
- (void)mountingManager:(RCTMountingManager *)mountingManager didMountComponentsWithRootTag:(ReactTag)rootTag
{
RCTIsMainQueue();
RCTFabricSurface *surface = [_surfaceRegistry surfaceForRootTag:rootTag];
// FIXME: Implement proper state propagation mechanism.
[surface _setStage:RCTSurfaceStageSurfaceDidInitialRendering];
[surface _setStage:RCTSurfaceStageSurfaceDidInitialLayout];
[surface _setStage:RCTSurfaceStageSurfaceDidInitialMounting];
UIView *rootComponentView = [_mountingManager.componentViewRegistry componentViewByTag:rootTag];
surface.view.rootView = (RCTSurfaceRootView *)rootComponentView;
}
#pragma mark - Bridge events
- (void)handleBridgeWillReloadNotification:(NSNotification *)notification
{
// TODO: Define a lifecycle contract for the pieces involved here including the scheduler, mounting manager, and
// the surface registry. For now simply recreate the scheduler on reload.
// The goal is to deallocate the Scheduler and its underlying references before the JS runtime is destroyed.
_scheduler = [[RCTScheduler alloc] init];
_scheduler.delegate = self;
}
- (void)handleJavaScriptDidLoadNotification:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _batchedBridge) {
_batchedBridge = bridge;
}
}
@end
@implementation RCTSurfacePresenter (Deprecated)
- (std::shared_ptr<FabricUIManager>)uiManager_DO_NOT_USE
{
return _scheduler.uiManager_DO_NOT_USE;
}
@end
@implementation RCTBridge (RCTSurfacePresenter)
- (RCTSurfacePresenter *)surfacePresenter
{
return [self jsBoundExtraModuleForClass:[RCTSurfacePresenter class]];
}
@end