mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Improve TM main queue initialization
Summary: In the legacy system, when NativeModules are supposed to be initialized on the main queue, we do the following synchronously on the main thread: 1. Done: Attach bridge on main queue 2. Register the NativeModule for frame updates on main queue 3. Post Notification that NativeModule was created on main queue 4. Attach methodQueue on main queue 5. Call new on main queue `[RCTModuleData instance]` is the entrypoint for all of this logic. We probably shouldn't synchronously execute all this initialization on the main queue, because it can lead to deadlocks down the road. Therefore, this diff makes it so that we still call `new` on the same thread. However, we do all other initialization in the main thread, if that's required. Changelog: [iOS][Fixed] TurboModule initialization on the main queue Reviewed By: PeteTheHeat Differential Revision: D17867583 fbshipit-source-id: a88412ee1e3d93a4f9b5ab0b4dd8fc5213fa91f8
This commit is contained in:
committed by
Facebook Github Bot
parent
e640637928
commit
bec5b8711f
@@ -15,6 +15,7 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTCxxModule.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTModuleData.h>
|
||||
#import <React/RCTPerformanceLogger.h>
|
||||
#import <ReactCommon/BridgeJSCallInvoker.h>
|
||||
#import <ReactCommon/TurboCxxModule.h>
|
||||
@@ -265,89 +266,111 @@ static Class getFallbackClassFromName(const char *name)
|
||||
_rctTurboModuleCache.insert({moduleName, module});
|
||||
}
|
||||
|
||||
/**
|
||||
* It is reasonable for NativeModules to not want/need the bridge.
|
||||
* In such cases, they won't have `@synthesize bridge = _bridge` in their
|
||||
* implementation, and a `- (RCTBridge *) bridge { ... }` method won't be
|
||||
* generated by the ObjC runtime. The property will also not be backed
|
||||
* by an ivar, which makes writing to it unsafe. Therefore, we check if
|
||||
* this method exists to know if we can safely set the bridge to the
|
||||
* NativeModule.
|
||||
*/
|
||||
if ([module respondsToSelector:@selector(bridge)] && _bridge) {
|
||||
/**
|
||||
* Just because a NativeModule has the `bridge` method, it doesn't mean
|
||||
* that it has synthesized the bridge in its implementation. Therefore,
|
||||
* we need to surround the code that sets the bridge to the NativeModule
|
||||
* inside a try/catch. This catches the cases where the NativeModule
|
||||
* author specifies a `bridge` method manually.
|
||||
*/
|
||||
@try {
|
||||
/**
|
||||
* If module requiresMainQueueSetup, dispatch to main queue. Bridge setup
|
||||
* may call APIs which are main queue only, which crash if called from
|
||||
* JS thread.
|
||||
*
|
||||
* RCTBridgeModule declares the bridge property as readonly.
|
||||
* Therefore, when authors of NativeModules synthesize the bridge
|
||||
* via @synthesize bridge = bridge;, the ObjC runtime generates
|
||||
* only a - (RCTBridge *) bridge: { ... } method. No setter is
|
||||
* generated, so we have have to rely on the KVC API of ObjC to set
|
||||
* the bridge property of these NativeModules.
|
||||
*/
|
||||
if ([[module class] respondsToSelector:@selector(requiresMainQueueSetup)] &&
|
||||
[[module class] requiresMainQueueSetup]) {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
[(id)module setValue:strongSelf->_bridge forKey:@"bridge"];
|
||||
});
|
||||
} else {
|
||||
[(id)module setValue:_bridge forKey:@"bridge"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
RCTLogError(
|
||||
@"%@ has no setter or ivar for its bridge, which is not "
|
||||
"permitted. You must either @synthesize the bridge property, "
|
||||
"or provide your own setter method.",
|
||||
RCTBridgeModuleNameForClass(module));
|
||||
}
|
||||
}
|
||||
__weak id<RCTBridgeModule> weakModule = (id<RCTBridgeModule>)module;
|
||||
__weak RCTBridge *weakBridge = _bridge;
|
||||
|
||||
/**
|
||||
* Some modules need their own queues, but don't provide any, so we need to create it for them.
|
||||
* These modules typically have the following:
|
||||
* `@synthesize methodQueue = _methodQueue`
|
||||
*/
|
||||
if ([module respondsToSelector:@selector(methodQueue)]) {
|
||||
dispatch_queue_t methodQueue = [module performSelector:@selector(methodQueue)];
|
||||
if (!methodQueue) {
|
||||
NSString *moduleClassName = NSStringFromClass(module.class);
|
||||
NSString *queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", moduleClassName];
|
||||
methodQueue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
|
||||
auto setupTurboModule = ^{
|
||||
if (!weakModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<RCTBridgeModule> strongModule = weakModule;
|
||||
RCTBridge *strongBridge = weakBridge;
|
||||
|
||||
/**
|
||||
* It is reasonable for NativeModules to not want/need the bridge.
|
||||
* In such cases, they won't have `@synthesize bridge = _bridge` in their
|
||||
* implementation, and a `- (RCTBridge *) bridge { ... }` method won't be
|
||||
* generated by the ObjC runtime. The property will also not be backed
|
||||
* by an ivar, which makes writing to it unsafe. Therefore, we check if
|
||||
* this method exists to know if we can safely set the bridge to the
|
||||
* NativeModule.
|
||||
*/
|
||||
if ([strongModule respondsToSelector:@selector(bridge)] && strongBridge) {
|
||||
/**
|
||||
* Just because a NativeModule has the `bridge` method, it doesn't mean
|
||||
* that it has synthesized the bridge in its implementation. Therefore,
|
||||
* we need to surround the code that sets the bridge to the NativeModule
|
||||
* inside a try/catch. This catches the cases where the NativeModule
|
||||
* author specifies a `bridge` method manually.
|
||||
*/
|
||||
@try {
|
||||
[(id)module setValue:methodQueue forKey:@"methodQueue"];
|
||||
/**
|
||||
* RCTBridgeModule declares the bridge property as readonly.
|
||||
* Therefore, when authors of NativeModules synthesize the bridge
|
||||
* via @synthesize bridge = bridge;, the ObjC runtime generates
|
||||
* only a - (RCTBridge *) bridge: { ... } method. No setter is
|
||||
* generated, so we have have to rely on the KVC API of ObjC to set
|
||||
* the bridge property of these NativeModules.
|
||||
*/
|
||||
[(id)strongModule setValue:strongBridge forKey:@"bridge"];
|
||||
} @catch (NSException *exception) {
|
||||
RCTLogError(
|
||||
@"TM: %@ is returning nil for its methodQueue, which is not "
|
||||
"permitted. You must either return a pre-initialized "
|
||||
"queue, or @synthesize the methodQueue to let the bridge "
|
||||
"create a queue for you.",
|
||||
moduleClassName);
|
||||
@"%@ has no setter or ivar for its bridge, which is not "
|
||||
"permitted. You must either @synthesize the bridge property, "
|
||||
"or provide your own setter method.",
|
||||
RCTBridgeModuleNameForClass(strongModule));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some modules need their own queues, but don't provide any, so we need to create it for them.
|
||||
* These modules typically have the following:
|
||||
* `@synthesize methodQueue = _methodQueue`
|
||||
*/
|
||||
if ([strongModule respondsToSelector:@selector(methodQueue)]) {
|
||||
dispatch_queue_t methodQueue = [strongModule performSelector:@selector(methodQueue)];
|
||||
if (!methodQueue) {
|
||||
NSString *moduleClassName = NSStringFromClass(strongModule.class);
|
||||
NSString *queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", moduleClassName];
|
||||
methodQueue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
|
||||
@try {
|
||||
[(id)strongModule setValue:methodQueue forKey:@"methodQueue"];
|
||||
} @catch (NSException *exception) {
|
||||
RCTLogError(
|
||||
@"TM: %@ is returning nil for its methodQueue, which is not "
|
||||
"permitted. You must either return a pre-initialized "
|
||||
"queue, or @synthesize the methodQueue to let the bridge "
|
||||
"create a queue for you.",
|
||||
moduleClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NativeModules that implement the RCTFrameUpdateObserver protocol
|
||||
* require registration with RCTDisplayLink.
|
||||
*
|
||||
* TODO(T55504345): Investigate whether we can improve this after TM
|
||||
* rollout.
|
||||
*/
|
||||
if (strongBridge) {
|
||||
RCTModuleData *data = [[RCTModuleData alloc] initWithModuleInstance:strongModule bridge:strongBridge];
|
||||
[strongBridge registerModuleForFrameUpdates:strongModule withModuleData:data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that this TurboModule was created.
|
||||
*
|
||||
* TODO(T41180176): Investigate whether we can delete this after TM
|
||||
* rollout.
|
||||
*/
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:RCTDidInitializeModuleNotification
|
||||
object:strongBridge
|
||||
userInfo:@{
|
||||
@"module" : module,
|
||||
@"bridge" : RCTNullIfNil(strongBridge == nil ? nil : strongBridge.parentBridge)
|
||||
}];
|
||||
};
|
||||
|
||||
if ([[module class] respondsToSelector:@selector(requiresMainQueueSetup)] &&
|
||||
[[module class] requiresMainQueueSetup]) {
|
||||
dispatch_async(dispatch_get_main_queue(), setupTurboModule);
|
||||
} else {
|
||||
setupTurboModule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that this TurboModule was created.
|
||||
*
|
||||
* TODO(T41180176): Investigate whether we can get rid of this after all
|
||||
* TurboModules are rolled out
|
||||
*/
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:RCTDidInitializeModuleNotification
|
||||
object:_bridge
|
||||
userInfo:@{@"module" : module, @"bridge" : RCTNullIfNil(_bridge.parentBridge)}];
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user