Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions packages/react-native-host/cocoa/RNXHostConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
#import <React/RCTBridgeDelegate.h>
#import <React/RCTLog.h>

#ifdef __cplusplus
#include <jsi/jsi.h>
#endif

#if __has_include(<ReactCommon/RCTTurboModuleManager.h>)
#import <ReactCommon/RCTTurboModuleManager.h>
#endif

@class ReactNativeHost;

NS_ASSUME_NONNULL_BEGIN

/// Configuration object for ``ReactNativeHost``.
Expand All @@ -19,6 +29,14 @@ NS_ASSUME_NONNULL_BEGIN
/// Returns whether the bridge should be released when the app is backgrounded.
@property (nonatomic, readonly) BOOL shouldReleaseBridgeWhenBackgrounded;

#if __has_include(<ReactCommon/RCTTurboModuleManager.h>)
/// Auxiliary ``RCTTurboModuleManagerDelegate`` consulted before
/// ``RNXTurboModuleAdapter``'s defaults. For each delegate method the
/// adapter implements, the auxiliary is called via ``respondsToSelector:``
/// first; returning ``nil`` / ``Nil`` falls through to default behavior.
@property (nonatomic, readonly, weak, nullable) id<RCTTurboModuleManagerDelegate> turboModuleManagerDelegate;
#endif

/// Logs a message.
- (void)logWithLevel:(RCTLogLevel)level
source:(RCTLogSource)source
Expand All @@ -30,6 +48,20 @@ NS_ASSUME_NONNULL_BEGIN
/// Handles a fatal error.
- (void)onFatalError:(NSError *)error;

/// Called after the JS instance has finished loading. ``error`` is ``nil``
/// on success.
- (void)host:(ReactNativeHost *)host didLoadInstanceWithError:(nullable NSError *)error
__attribute__((__swift_name__("host(_:didLoadInstanceWithError:)")));

/// Called when the instance is about to be unloaded.
- (void)hostWillUnloadInstance:(ReactNativeHost *)host;

#ifdef __cplusplus
/// Called after host bindings install but before the user JS bundle loads.
/// Use to evaluate pre-user JS on the runtime. Bridgeless mode only.
- (void)host:(ReactNativeHost *)host didInitializeRuntime:(facebook::jsi::Runtime &)runtime;
#endif // __cplusplus

// MARK: - RCTBridgeDelegate deprecated details (for backwards compatibility) [>=0.84]

- (NSURL *__nullable)sourceURLForBridge:(RCTBridge *)bridge;
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native-host/cocoa/RNXTurboModuleAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#endif // USE_FABRIC

@class RCTBridge;
@protocol RNXHostConfig;

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -17,6 +18,10 @@ NS_ASSUME_NONNULL_BEGIN
@interface RNXTurboModuleAdapter : NSObject
#endif // USE_FABRIC

/// Weak reference to the host's config; powers consultation of
/// ``RNXHostConfig.turboModuleManagerDelegate``.
@property (nonatomic, weak, nullable) id<RNXHostConfig> hostConfig;

- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:
(RCTBridge *)bridge;

Expand Down
22 changes: 22 additions & 0 deletions packages/react-native-host/cocoa/RNXTurboModuleAdapter.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#import "RNXTurboModuleAdapter.h"

#import "RNXHostConfig.h"

#include "FollyConfig.h"

#if USE_FABRIC
Expand Down Expand Up @@ -103,6 +105,16 @@ - (instancetype)init

- (Class)getModuleClassFromName:(char const *)name
{
id<RNXHostConfig> config = _hostConfig;
if ([config respondsToSelector:@selector(turboModuleManagerDelegate)]) {
id<RCTTurboModuleManagerDelegate> aux = [config turboModuleManagerDelegate];
if ([aux respondsToSelector:_cmd]) {
Class cls = [aux getModuleClassFromName:name];
if (cls != Nil) {
return cls;
}
}
}
return RCTCoreModulesClassProvider(name);
}

Expand All @@ -120,6 +132,16 @@ - (Class)getModuleClassFromName:(char const *)name

- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
id<RNXHostConfig> config = _hostConfig;
if ([config respondsToSelector:@selector(turboModuleManagerDelegate)]) {
id<RCTTurboModuleManagerDelegate> aux = [config turboModuleManagerDelegate];
if ([aux respondsToSelector:_cmd]) {
id<RCTTurboModule> instance = [aux getModuleInstanceFromClass:moduleClass];
if (instance != nil) {
return instance;
}
}
}
#if USE_OSS_CODEGEN
return RCTAppSetupDefaultModuleFromClass(moduleClass, [RCTAppDependencyProvider new]);
#elif __has_include(<React-RCTAppDelegate/RCTDependencyProvider.h>) || __has_include(<React_RCTAppDelegate/RCTDependencyProvider.h>)
Expand Down
99 changes: 97 additions & 2 deletions packages/react-native-host/cocoa/ReactNativeHost.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,41 @@ @interface ReactNativeHost () <RCTComponentViewFactoryComponentProvider>
@end
#endif // USE_CODEGEN_PROVIDER

#if USE_BRIDGELESS

// Forwards host:didInitializeRuntime: from RCTHost to the consumer's RNXHostConfig.
@interface _RNXForwardingRCTHostDelegate : NSObject <RCTHostDelegate>
- (instancetype)initWithHost:(ReactNativeHost *)host config:(id<RNXHostConfig>)config;
@end

@implementation _RNXForwardingRCTHostDelegate {
__weak ReactNativeHost *_host;
__weak id<RNXHostConfig> _config;
}

- (instancetype)initWithHost:(ReactNativeHost *)host config:(id<RNXHostConfig>)config
{
if (self = [super init]) {
_host = host;
_config = config;
}
return self;
}

- (void)host:(RCTHost *)host didInitializeRuntime:(facebook::jsi::Runtime &)runtime
{
id<RNXHostConfig> config = _config;
ReactNativeHost *forwardedHost = _host;
if (forwardedHost != nil &&
[config respondsToSelector:@selector(host:didInitializeRuntime:)]) {
[config host:forwardedHost didInitializeRuntime:runtime];
}
}

@end

#endif // USE_BRIDGELESS

@implementation ReactNativeHost {
__weak id<RNXHostConfig> _config;
NSDictionary *_launchOptions;
Expand All @@ -44,6 +79,9 @@ @implementation ReactNativeHost {
RCTHost *_reactHost;
NSLock *_isShuttingDown;
RNXHostReleaser *_hostReleaser;
#if USE_BRIDGELESS
_RNXForwardingRCTHostDelegate *_hostDelegateProxy;
#endif // USE_BRIDGELESS
#ifdef USE_REACT_NATIVE_CONFIG
std::shared_ptr<ReactNativeConfig> _reactNativeConfig;
#endif // USE_REACT_NATIVE_CONFIG
Expand Down Expand Up @@ -99,11 +137,63 @@ - (instancetype)initWithConfig:(id<RNXHostConfig>)config launchOptions:(NSDictio
});
}

if ([config respondsToSelector:@selector(host:didLoadInstanceWithError:)]) {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(_rnxInstanceDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(_rnxInstanceDidFailToLoad:)
name:RCTJavaScriptDidFailToLoadNotification
object:nil];
}
if ([config respondsToSelector:@selector(hostWillUnloadInstance:)]) {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(_rnxInstanceWillUnload:)
name:RCTBridgeWillBeInvalidatedNotification
object:nil];
}

[self initializeReactHost];
}
return self;
}

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)_rnxInstanceDidLoad:(NSNotification *)__unused notification
{
id<RNXHostConfig> config = _config;
if ([config respondsToSelector:@selector(host:didLoadInstanceWithError:)]) {
[config host:self didLoadInstanceWithError:nil];
}
}

- (void)_rnxInstanceDidFailToLoad:(NSNotification *)notification
{
id<RNXHostConfig> config = _config;
if ([config respondsToSelector:@selector(host:didLoadInstanceWithError:)]) {
NSError *error = notification.userInfo[@"error"];
[config host:self didLoadInstanceWithError:error ?: [NSError errorWithDomain:@"ReactNativeHost"
code:0
userInfo:nil]];
}
}

- (void)_rnxInstanceWillUnload:(NSNotification *)__unused notification
{
id<RNXHostConfig> config = _config;
if ([config respondsToSelector:@selector(hostWillUnloadInstance:)]) {
[config hostWillUnloadInstance:self];
}
}

- (RCTBridge *)bridge
{
if (self.isBridgelessEnabled) {
Expand Down Expand Up @@ -264,6 +354,7 @@ - (void)enableTurboModule
{
#if USE_FABRIC
_turboModuleAdapter = [[RNXTurboModuleAdapter alloc] init];
_turboModuleAdapter.hostConfig = _config;
RCTEnableTurboModule(true);
#endif
}
Expand Down Expand Up @@ -303,6 +394,10 @@ - (void)initializeReactHost
#endif
};

// Retained as an ivar because RCTHost stores host delegates weakly.
_hostDelegateProxy = [[_RNXForwardingRCTHostDelegate alloc] initWithHost:self
config:_config];

__weak __typeof(self) weakSelf = self;
if ([RCTHost instancesRespondToSelector:@selector
(initWithBundleURLProvider:
Expand All @@ -312,13 +407,13 @@ - (void)initializeReactHost
initWithBundleURLProvider:^{
return [weakSelf sourceURLForBridge:nil];
}
hostDelegate:nil
hostDelegate:_hostDelegateProxy
turboModuleManagerDelegate:_turboModuleAdapter
jsEngineProvider:jsEngineProvider
launchOptions:_launchOptions];
} else {
_reactHost = [[RCTHost alloc] initWithBundleURL:[self sourceURLForBridge:nil]
hostDelegate:nil
hostDelegate:_hostDelegateProxy
turboModuleManagerDelegate:_turboModuleAdapter
jsEngineProvider:jsEngineProvider];
}
Expand Down
Loading