diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm index c79431be8cf..977c049a2a8 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm @@ -15,6 +15,7 @@ #import #import #import +#import #import #import #import @@ -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 weakModule = (id)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 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; }