/
RCTTurboModuleManager.mm
500 lines (427 loc) · 17 KB
/
RCTTurboModuleManager.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/*
* 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 "RCTTurboModuleManager.h"
#import <atomic>
#import <cassert>
#import <mutex>
#import <React/RCTBridge+Private.h>
#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>
#import <ReactCommon/TurboModuleBinding.h>
using namespace facebook;
// Fallback lookup since RCT class prefix is sometimes stripped in the existing NativeModule system.
// This will be removed in the future.
static Class getFallbackClassFromName(const char *name)
{
Class moduleClass = NSClassFromString([NSString stringWithUTF8String:name]);
if (!moduleClass) {
moduleClass = NSClassFromString([NSString stringWithFormat:@"RCT%s", name]);
}
return moduleClass;
}
@implementation RCTTurboModuleManager {
jsi::Runtime *_runtime;
std::shared_ptr<facebook::react::CallInvoker> _jsInvoker;
std::shared_ptr<react::TurboModuleBinding> _binding;
__weak id<RCTTurboModuleManagerDelegate> _delegate;
__weak RCTBridge *_bridge;
/**
* TODO(T48018690):
* All modules are currently long-lived.
* We need to come up with a mechanism to allow modules to specify whether
* they want to be long-lived or short-lived.
*/
std::unordered_map<std::string, id<RCTTurboModule>> _rctTurboModuleCache;
std::unordered_map<std::string, std::shared_ptr<react::TurboModule>> _turboModuleCache;
/**
* _rctTurboModuleCache can be accessed by multiple threads at once via
* the provideRCTTurboModule method. This can lead to races. Therefore, we
* need to protect access to this unordered_map.
*
* Note:
* There's no need to protect access to _turboModuleCache because that cache
* is only accessed within provideTurboModule, which is only invoked by the
* JS thread.
*/
std::mutex _rctTurboModuleCacheLock;
std::atomic<bool> _invalidating;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge delegate:(id<RCTTurboModuleManagerDelegate>)delegate
{
if (self = [super init]) {
_jsInvoker = std::make_shared<react::BridgeJSCallInvoker>(bridge.reactInstance);
_delegate = delegate;
_bridge = bridge;
_invalidating = false;
// Necessary to allow NativeModules to lookup TurboModules
[bridge setRCTTurboModuleLookupDelegate:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeWillInvalidateModules:)
name:RCTBridgeWillInvalidateModulesNotification
object:_bridge.parentBridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidInvalidateModules:)
name:RCTBridgeDidInvalidateModulesNotification
object:_bridge.parentBridge];
__weak __typeof(self) weakSelf = self;
auto moduleProvider = [weakSelf](const std::string &name) -> std::shared_ptr<react::TurboModule> {
if (!weakSelf) {
return nullptr;
}
__strong __typeof(self) strongSelf = weakSelf;
auto moduleName = name.c_str();
auto moduleWasNotInitialized = ![strongSelf moduleIsInitialized:moduleName];
if (moduleWasNotInitialized) {
[strongSelf->_bridge.performanceLogger markStartForTag:RCTPLTurboModuleSetup];
}
/**
* By default, all TurboModules are long-lived.
* Additionally, if a TurboModule with the name `name` isn't found, then we
* trigger an assertion failure.
*/
auto turboModule = [strongSelf provideTurboModule:moduleName];
if (moduleWasNotInitialized && [strongSelf moduleIsInitialized:moduleName]) {
[strongSelf->_bridge.performanceLogger markStopForTag:RCTPLTurboModuleSetup];
[strongSelf notifyAboutTurboModuleSetup:moduleName];
}
return turboModule;
};
_binding = std::make_shared<react::TurboModuleBinding>(moduleProvider);
}
return self;
}
- (void)notifyAboutTurboModuleSetup:(const char *)name
{
NSString *moduleName = [[NSString alloc] initWithUTF8String:name];
if (moduleName) {
int64_t setupTime = [self->_bridge.performanceLogger durationForTag:RCTPLTurboModuleSetup];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidSetupModuleNotification
object:nil
userInfo:@{
RCTDidSetupModuleNotificationModuleNameKey : moduleName,
RCTDidSetupModuleNotificationSetupTimeKey : @(setupTime)
}];
}
}
/**
* Given a name for a TurboModule, return a C++ object which is the instance
* of that TurboModule C++ class. This class wraps the TurboModule's ObjC instance.
* If no TurboModule ObjC class exist with the provided name, abort program.
*
* Note: All TurboModule instances are cached, which means they're all long-lived
* (for now).
*/
- (std::shared_ptr<react::TurboModule>)provideTurboModule:(const char *)moduleName
{
auto turboModuleLookup = _turboModuleCache.find(moduleName);
if (turboModuleLookup != _turboModuleCache.end()) {
return turboModuleLookup->second;
}
/**
* Step 1: Look for pure C++ modules.
* Pure C++ modules get priority.
*/
if ([_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
auto turboModule = [_delegate getTurboModule:moduleName jsInvoker:_jsInvoker];
if (turboModule != nullptr) {
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
}
/**
* Step 2: Look for platform-specific modules.
*/
id<RCTTurboModule> module = [self provideRCTTurboModule:moduleName];
// If we request that a TurboModule be created, its respective ObjC class must exist
// If the class doesn't exist, then provideRCTTurboModule returns nil
if (!module) {
return nullptr;
}
Class moduleClass = [module class];
// If RCTTurboModule supports creating its own C++ TurboModule object,
// allow it to do so.
if ([module respondsToSelector:@selector(getTurboModuleWithJsInvoker:)]) {
auto turboModule = [module getTurboModuleWithJsInvoker:_jsInvoker];
assert(turboModule != nullptr);
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
/**
* Step 2c: If the moduleClass is a legacy CxxModule, return a TurboCxxModule instance that
* wraps CxxModule.
*/
if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) {
// Use TurboCxxModule compat class to wrap the CxxModule instance.
// This is only for migration convenience, despite less performant.
auto turboModule = std::make_shared<react::TurboCxxModule>([((RCTCxxModule *)module) createModule], _jsInvoker);
_turboModuleCache.insert({moduleName, turboModule});
return turboModule;
}
/**
* Step 2d: Return an exact sub-class of ObjC TurboModule
*/
auto turboModule = [_delegate getTurboModule:moduleName instance:module jsInvoker:_jsInvoker];
if (turboModule != nullptr) {
_turboModuleCache.insert({moduleName, turboModule});
}
return turboModule;
}
/**
* Given a name for a TurboModule, return an ObjC object which is the instance
* of that TurboModule ObjC class. If no TurboModule exist with the provided name,
* return nil.
*
* Note: All TurboModule instances are cached, which means they're all long-lived
* (for now).
*/
- (id<RCTTurboModule>)provideRCTTurboModule:(const char *)moduleName
{
Class moduleClass;
id<RCTTurboModule> module = nil;
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
auto rctTurboModuleCacheLookup = _rctTurboModuleCache.find(moduleName);
if (rctTurboModuleCacheLookup != _rctTurboModuleCache.end()) {
return rctTurboModuleCacheLookup->second;
}
if (_invalidating) {
// Don't allow creating new instances while invalidating.
return nil;
}
/**
* Step 2a: Resolve platform-specific class.
*/
if ([_delegate respondsToSelector:@selector(getModuleClassFromName:)]) {
moduleClass = [_delegate getModuleClassFromName:moduleName];
}
if (!moduleClass) {
moduleClass = getFallbackClassFromName(moduleName);
}
if (![moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) {
return nil;
}
/**
* Step 2b: Ask hosting application/delegate to instantiate this class
*/
if ([_delegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) {
module = [_delegate getModuleInstanceFromClass:moduleClass];
} else {
module = [moduleClass new];
}
if ([module respondsToSelector:@selector(setTurboModuleLookupDelegate:)]) {
[module setTurboModuleLookupDelegate:self];
}
_rctTurboModuleCache.insert({moduleName, module});
}
__weak id<RCTBridgeModule> weakModule = (id<RCTBridgeModule>)module;
__weak RCTBridge *weakBridge = _bridge;
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 {
/**
* 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(
@"%@ 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 class]));
}
}
/**
* 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 parentBridge])
}];
};
if ([[module class] respondsToSelector:@selector(requiresMainQueueSetup)] &&
[[module class] requiresMainQueueSetup]) {
dispatch_async(dispatch_get_main_queue(), setupTurboModule);
} else {
setupTurboModule();
}
return module;
}
- (void)installJSBindingWithRuntime:(jsi::Runtime *)runtime
{
_runtime = runtime;
if (!_runtime) {
// jsi::Runtime doesn't exist when attached to Chrome debugger.
return;
}
react::TurboModuleBinding::install(*_runtime, _binding);
}
- (std::shared_ptr<facebook::react::TurboModule>)getModule:(const std::string &)name
{
return _binding->getModule(name);
}
#pragma mark RCTTurboModuleLookupDelegate
- (id)moduleForName:(const char *)moduleName
{
return [self moduleForName:moduleName warnOnLookupFailure:YES];
}
- (id)moduleForName:(const char *)moduleName warnOnLookupFailure:(BOOL)warnOnLookupFailure
{
id<RCTTurboModule> module = [self provideRCTTurboModule:moduleName];
if (warnOnLookupFailure && !module) {
RCTLogError(@"Unable to find module for %@", [NSString stringWithUTF8String:moduleName]);
}
return module;
}
- (BOOL)moduleIsInitialized:(const char *)moduleName
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
return _rctTurboModuleCache.find(std::string(moduleName)) != _rctTurboModuleCache.end();
}
#pragma mark Invalidation logic
- (void)bridgeWillInvalidateModules:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _bridge) {
return;
}
_invalidating = true;
}
- (void)bridgeDidInvalidateModules:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _bridge) {
return;
}
std::unordered_map<std::string, id<RCTTurboModule>> rctCacheCopy;
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
rctCacheCopy.insert(_rctTurboModuleCache.begin(), _rctTurboModuleCache.end());
}
// Backward-compatibility: RCTInvalidating handling.
dispatch_group_t moduleInvalidationGroup = dispatch_group_create();
for (const auto &p : rctCacheCopy) {
id<RCTTurboModule> module = p.second;
if ([module respondsToSelector:@selector(invalidate)]) {
if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t methodQueue = [module performSelector:@selector(methodQueue)];
if (methodQueue) {
dispatch_group_enter(moduleInvalidationGroup);
[bridge
dispatchBlock:^{
[((id<RCTInvalidating>)module) invalidate];
dispatch_group_leave(moduleInvalidationGroup);
}
queue:methodQueue];
continue;
}
}
[((id<RCTInvalidating>)module) invalidate];
}
}
if (dispatch_group_wait(moduleInvalidationGroup, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) {
RCTLogError(@"TurboModuleManager: Timed out waiting for modules to be invalidated");
}
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
_rctTurboModuleCache.clear();
}
_turboModuleCache.clear();
_binding->invalidate();
}
- (void)invalidate
{
std::unordered_map<std::string, id<RCTTurboModule>> rctCacheCopy;
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
rctCacheCopy.insert(_rctTurboModuleCache.begin(), _rctTurboModuleCache.end());
}
// Backward-compatibility: RCTInvalidating handling, but not adhering to desired methodQueue.
for (const auto &p : rctCacheCopy) {
id<RCTTurboModule> module = p.second;
if ([module respondsToSelector:@selector(invalidate)]) {
[((id<RCTInvalidating>)module) invalidate];
}
}
{
std::unique_lock<std::mutex> lock(_rctTurboModuleCacheLock);
_rctTurboModuleCache.clear();
}
_turboModuleCache.clear();
_binding->invalidate();
}
@end