mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
80f5b6c110
Summary: In https://github.com/facebook/react-native/issues/25427, radex added initial support for running React Native projects on macOS via Catalyst. However, `RCTWebSocket` was disabled for that target because of some compilation issues. This meant that running projects via a connection to the packager wasn't possible: no live reload, and projects must be run in "Release" mode. It also meant making manual changes to Xcode projects deploying to macOS and scattering a number of conditional checks throughout the codebase. In this change, I've implemented support for `RCTWebSocket` on the macOS target and re-enabled the affected features. Live reload and the inspector now work for macOS targets. Manual modifications of Xcode build settings are no longer necessary for react-native projects running on macOS.  ### Limitations There's no binding which displays the developer menu (since there's no shake event on macOS). We'll probably want to add one, perhaps to the menu bar. I've chosen not to commit the modifications to RNTester which enable macOS support, since that would imply more "official" support for this target than I suspect you all would like to convey. I'm happy to add those chunks if it would be helpful. ## Changelog [iOS] [Added] - Added web socket support for macOS (Catalyst), enabling debug builds and live reload Pull Request resolved: https://github.com/facebook/react-native/pull/27469 Test Plan: * Open RNTester/RNTester.xcodeproj with Xcode 11.2.1, run it like a normal iOS app -- make sure it compiles and runs correctly (no regression) * Select "My Mac" as device target, and run. You may need to configure a valid development team to make signing work. * RNTester should run fine with no additional configuration. Modify a file in RNTester, note that live reload is now working. * Test the developer inspector. To display the developer menu, you'll need to manually show it; here's an example diff which does that: ``` diff --git a/RNTester/js/RNTesterApp.ios.js b/RNTester/js/RNTesterApp.ios.js index 8245a68d12..a447ad3b1b 100644 --- a/RNTester/js/RNTesterApp.ios.js +++ b/RNTester/js/RNTesterApp.ios.js @@ -19,6 +19,8 @@ const React = require('react'); const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios'); const URIActionMap = require('./utils/URIActionMap'); +import NativeDevMenu from '../../Libraries/NativeModules/specs/NativeDevMenu'; + const { AppRegistry, AsyncStorage, @@ -143,6 +145,7 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> { UNSAFE_componentWillMount() { BackHandler.addEventListener('hardwareBackPress', this._handleBack); + NativeDevMenu.show(); } componentDidMount() { ``` Reviewed By: sammy-SC Differential Revision: D18945861 Pulled By: hramos fbshipit-source-id: edcf02c5803742c89a845a3e5d72bc7dacae839f
446 lines
13 KiB
Objective-C
446 lines
13 KiB
Objective-C
/*
|
|
* 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 "RCTBridge.h"
|
|
#import "RCTBridge+Private.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import "RCTConvert.h"
|
|
#import "RCTEventDispatcher.h"
|
|
#if RCT_ENABLE_INSPECTOR
|
|
#import "RCTInspectorDevServerHelper.h"
|
|
#endif
|
|
#import "RCTLog.h"
|
|
#import "RCTModuleData.h"
|
|
#import "RCTPerformanceLogger.h"
|
|
#import "RCTProfile.h"
|
|
#import "RCTReloadCommand.h"
|
|
#import "RCTUtils.h"
|
|
|
|
NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
|
|
NSString *const RCTJavaScriptWillStartExecutingNotification = @"RCTJavaScriptWillStartExecutingNotification";
|
|
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
|
|
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
|
|
NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification";
|
|
NSString *const RCTDidSetupModuleNotification = @"RCTDidSetupModuleNotification";
|
|
NSString *const RCTDidSetupModuleNotificationModuleNameKey = @"moduleName";
|
|
NSString *const RCTDidSetupModuleNotificationSetupTimeKey = @"setupTime";
|
|
NSString *const RCTBridgeWillReloadNotification = @"RCTBridgeWillReloadNotification";
|
|
NSString *const RCTBridgeFastRefreshNotification = @"RCTBridgeFastRefreshNotification";
|
|
NSString *const RCTBridgeWillDownloadScriptNotification = @"RCTBridgeWillDownloadScriptNotification";
|
|
NSString *const RCTBridgeDidDownloadScriptNotification = @"RCTBridgeDidDownloadScriptNotification";
|
|
NSString *const RCTBridgeWillInvalidateModulesNotification = @"RCTBridgeWillInvalidateModulesNotification";
|
|
NSString *const RCTBridgeDidInvalidateModulesNotification = @"RCTBridgeDidInvalidateModulesNotification";
|
|
NSString *const RCTBridgeWillBeInvalidatedNotification = @"RCTBridgeWillBeInvalidatedNotification";
|
|
NSString *const RCTBridgeDidDownloadScriptNotificationSourceKey = @"source";
|
|
NSString *const RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey = @"bridgeDescription";
|
|
|
|
static NSMutableArray<Class> *RCTModuleClasses;
|
|
static dispatch_queue_t RCTModuleClassesSyncQueue;
|
|
NSArray<Class> *RCTGetModuleClasses(void)
|
|
{
|
|
__block NSArray<Class> *result;
|
|
dispatch_sync(RCTModuleClassesSyncQueue, ^{
|
|
result = [RCTModuleClasses copy];
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Register the given class as a bridge module. All modules must be registered
|
|
* prior to the first bridge initialization.
|
|
*/
|
|
void RCTRegisterModule(Class);
|
|
void RCTRegisterModule(Class moduleClass)
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
RCTModuleClasses = [NSMutableArray new];
|
|
RCTModuleClassesSyncQueue = dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
|
|
});
|
|
|
|
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
|
|
@"%@ does not conform to the RCTBridgeModule protocol",
|
|
moduleClass);
|
|
|
|
// Register module
|
|
dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
|
|
[RCTModuleClasses addObject:moduleClass];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This function returns the module name for a given class.
|
|
*/
|
|
NSString *RCTBridgeModuleNameForClass(Class cls)
|
|
{
|
|
#if RCT_DEBUG
|
|
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
|
|
@"Bridge module `%@` does not conform to RCTBridgeModule", cls);
|
|
#endif
|
|
|
|
NSString *name = [cls moduleName];
|
|
if (name.length == 0) {
|
|
name = NSStringFromClass(cls);
|
|
}
|
|
|
|
return RCTDropReactPrefixes(name);
|
|
}
|
|
|
|
static BOOL turboModuleEnabled = NO;
|
|
BOOL RCTTurboModuleEnabled(void)
|
|
{
|
|
#if RCT_DEBUG
|
|
// TODO(T53341772): Allow TurboModule for test environment. Right now this breaks RNTester tests if enabled.
|
|
if (RCTRunningInTestEnvironment()) {
|
|
return NO;
|
|
}
|
|
#endif
|
|
return turboModuleEnabled;
|
|
}
|
|
|
|
void RCTEnableTurboModule(BOOL enabled) {
|
|
turboModuleEnabled = enabled;
|
|
}
|
|
|
|
#if RCT_DEBUG
|
|
void RCTVerifyAllModulesExported(NSArray *extraModules)
|
|
{
|
|
// Check for unexported modules
|
|
unsigned int classCount;
|
|
Class *classes = objc_copyClassList(&classCount);
|
|
|
|
NSMutableSet *moduleClasses = [NSMutableSet new];
|
|
[moduleClasses addObjectsFromArray:RCTGetModuleClasses()];
|
|
[moduleClasses addObjectsFromArray:[extraModules valueForKeyPath:@"class"]];
|
|
|
|
for (unsigned int i = 0; i < classCount; i++) {
|
|
Class cls = classes[i];
|
|
if (strncmp(class_getName(cls), "RCTCxxModule", strlen("RCTCxxModule")) == 0) {
|
|
continue;
|
|
}
|
|
Class superclass = cls;
|
|
while (superclass) {
|
|
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule))) {
|
|
if ([moduleClasses containsObject:cls]) {
|
|
break;
|
|
}
|
|
|
|
// Verify it's not a super-class of one of our moduleClasses
|
|
BOOL isModuleSuperClass = NO;
|
|
for (Class moduleClass in moduleClasses) {
|
|
if ([moduleClass isSubclassOfClass:cls]) {
|
|
isModuleSuperClass = YES;
|
|
break;
|
|
}
|
|
}
|
|
if (isModuleSuperClass) {
|
|
break;
|
|
}
|
|
|
|
// Note: Some modules may be lazily loaded and not exported up front, so this message is no longer a warning.
|
|
RCTLogInfo(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", cls);
|
|
break;
|
|
}
|
|
superclass = class_getSuperclass(superclass);
|
|
}
|
|
}
|
|
|
|
free(classes);
|
|
}
|
|
#endif
|
|
|
|
@interface RCTBridge () <RCTReloadListener>
|
|
@end
|
|
|
|
@implementation RCTBridge
|
|
{
|
|
NSURL *_delegateBundleURL;
|
|
}
|
|
|
|
dispatch_queue_t RCTJSThread;
|
|
|
|
+ (void)initialize
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
// Set up JS thread
|
|
RCTJSThread = (id)kCFNull;
|
|
});
|
|
}
|
|
|
|
static RCTBridge *RCTCurrentBridgeInstance = nil;
|
|
|
|
/**
|
|
* The last current active bridge instance. This is set automatically whenever
|
|
* the bridge is accessed. It can be useful for static functions or singletons
|
|
* that need to access the bridge for purposes such as logging, but should not
|
|
* be relied upon to return any particular instance, due to race conditions.
|
|
*/
|
|
+ (instancetype)currentBridge
|
|
{
|
|
return RCTCurrentBridgeInstance;
|
|
}
|
|
|
|
+ (void)setCurrentBridge:(RCTBridge *)currentBridge
|
|
{
|
|
RCTCurrentBridgeInstance = currentBridge;
|
|
}
|
|
|
|
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
|
|
launchOptions:(NSDictionary *)launchOptions
|
|
{
|
|
return [self initWithDelegate:delegate
|
|
bundleURL:nil
|
|
moduleProvider:nil
|
|
launchOptions:launchOptions];
|
|
}
|
|
|
|
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
|
moduleProvider:(RCTBridgeModuleListProvider)block
|
|
launchOptions:(NSDictionary *)launchOptions
|
|
{
|
|
return [self initWithDelegate:nil
|
|
bundleURL:bundleURL
|
|
moduleProvider:block
|
|
launchOptions:launchOptions];
|
|
}
|
|
|
|
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
|
|
bundleURL:(NSURL *)bundleURL
|
|
moduleProvider:(RCTBridgeModuleListProvider)block
|
|
launchOptions:(NSDictionary *)launchOptions
|
|
{
|
|
if (self = [super init]) {
|
|
_delegate = delegate;
|
|
_bundleURL = bundleURL;
|
|
_moduleProvider = block;
|
|
_launchOptions = [launchOptions copy];
|
|
|
|
[self setUp];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|
|
|
- (void)dealloc
|
|
{
|
|
/**
|
|
* This runs only on the main thread, but crashes the subclass
|
|
* RCTAssertMainQueue();
|
|
*/
|
|
[self invalidate];
|
|
}
|
|
|
|
- (void)setRCTTurboModuleLookupDelegate:(id<RCTTurboModuleLookupDelegate>)turboModuleLookupDelegate
|
|
{
|
|
[self.batchedBridge setRCTTurboModuleLookupDelegate:turboModuleLookupDelegate];
|
|
}
|
|
|
|
- (void)didReceiveReloadCommand
|
|
{
|
|
[self reloadWithReason:@"Command"];
|
|
}
|
|
|
|
- (NSArray<Class> *)moduleClasses
|
|
{
|
|
return self.batchedBridge.moduleClasses;
|
|
}
|
|
|
|
- (id)moduleForName:(NSString *)moduleName
|
|
{
|
|
return [self.batchedBridge moduleForName:moduleName];
|
|
}
|
|
|
|
- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
|
|
{
|
|
return [self.batchedBridge moduleForName:moduleName lazilyLoadIfNecessary:lazilyLoad];
|
|
}
|
|
|
|
- (id)moduleForClass:(Class)moduleClass
|
|
{
|
|
id module = [self.batchedBridge moduleForClass:moduleClass];
|
|
if (!module) {
|
|
module = [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
|
|
}
|
|
return module;
|
|
}
|
|
|
|
- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol
|
|
{
|
|
NSMutableArray *modules = [NSMutableArray new];
|
|
for (Class moduleClass in [self.moduleClasses copy]) {
|
|
if ([moduleClass conformsToProtocol:protocol]) {
|
|
id module = [self moduleForClass:moduleClass];
|
|
if (module) {
|
|
[modules addObject:module];
|
|
}
|
|
}
|
|
}
|
|
return [modules copy];
|
|
}
|
|
|
|
- (BOOL)moduleIsInitialized:(Class)moduleClass
|
|
{
|
|
return [self.batchedBridge moduleIsInitialized:moduleClass];
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED - please use RCTReloadCommand.
|
|
*/
|
|
- (void)reload
|
|
{
|
|
[self reloadWithReason:@"Unknown from bridge"];
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED - please use RCTReloadCommand.
|
|
*/
|
|
- (void)reloadWithReason:(NSString *)reason
|
|
{
|
|
#if RCT_ENABLE_INSPECTOR
|
|
// Disable debugger to resume the JsVM & avoid thread locks while reloading
|
|
[RCTInspectorDevServerHelper disableDebugger];
|
|
#endif
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillReloadNotification
|
|
object:self
|
|
userInfo:nil];
|
|
|
|
/**
|
|
* Any thread
|
|
*/
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// WARNING: Invalidation is async, so it may not finish before re-setting up the bridge,
|
|
// causing some issues. TODO: revisit this post-Fabric/TurboModule.
|
|
[self invalidate];
|
|
// Reload is a special case, do not preserve launchOptions and treat reload as a fresh start
|
|
self->_launchOptions = nil;
|
|
[self setUp];
|
|
});
|
|
}
|
|
|
|
- (void)onFastRefresh
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeFastRefreshNotification object:self];
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED - please use RCTReloadCommand.
|
|
*/
|
|
- (void)requestReload
|
|
{
|
|
[self reloadWithReason:@"Requested from bridge"];
|
|
}
|
|
|
|
- (Class)bridgeClass
|
|
{
|
|
return [RCTCxxBridge class];
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil);
|
|
|
|
_performanceLogger = [RCTPerformanceLogger new];
|
|
[_performanceLogger markStartForTag:RCTPLBridgeStartup];
|
|
[_performanceLogger markStartForTag:RCTPLTTI];
|
|
|
|
Class bridgeClass = self.bridgeClass;
|
|
|
|
// Only update bundleURL from delegate if delegate bundleURL has changed
|
|
NSURL *previousDelegateURL = _delegateBundleURL;
|
|
_delegateBundleURL = [self.delegate sourceURLForBridge:self];
|
|
if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
|
|
_bundleURL = _delegateBundleURL;
|
|
}
|
|
|
|
// Sanitize the bundle URL
|
|
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
|
|
|
|
RCTExecuteOnMainQueue(^{
|
|
RCTRegisterReloadCommandListener(self);
|
|
RCTReloadCommandSetBundleURL(self->_bundleURL);
|
|
});
|
|
|
|
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
|
|
[self.batchedBridge start];
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
- (BOOL)isLoading
|
|
{
|
|
return self.batchedBridge.loading;
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
return self.batchedBridge.valid;
|
|
}
|
|
|
|
- (BOOL)isBatchActive
|
|
{
|
|
return [_batchedBridge isBatchActive];
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillBeInvalidatedNotification
|
|
object:self];
|
|
|
|
RCTBridge *batchedBridge = self.batchedBridge;
|
|
self.batchedBridge = nil;
|
|
|
|
if (batchedBridge) {
|
|
RCTExecuteOnMainQueue(^{
|
|
[batchedBridge invalidate];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)updateModuleWithInstance:(id<RCTBridgeModule>)instance
|
|
{
|
|
[self.batchedBridge updateModuleWithInstance:instance];
|
|
}
|
|
|
|
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules
|
|
{
|
|
[self.batchedBridge registerAdditionalModuleClasses:modules];
|
|
}
|
|
|
|
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
|
{
|
|
NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];
|
|
NSString *module = ids[0];
|
|
NSString *method = ids[1];
|
|
[self enqueueJSCall:module method:method args:args completion:NULL];
|
|
}
|
|
|
|
- (void)enqueueJSCall:(NSString *)module
|
|
method:(NSString *)method
|
|
args:(NSArray *)args
|
|
completion:(dispatch_block_t)completion
|
|
{
|
|
[self.batchedBridge enqueueJSCall:module method:method args:args completion:completion];
|
|
}
|
|
|
|
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
|
|
{
|
|
[self.batchedBridge enqueueCallback:cbID args:args];
|
|
}
|
|
|
|
- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path
|
|
{
|
|
[self.batchedBridge registerSegmentWithId:segmentId path:path];
|
|
}
|
|
|
|
@end
|