-
Notifications
You must be signed in to change notification settings - Fork 24.3k
/
RCTTurboModuleManager.mm
286 lines (244 loc) · 9.47 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
/**
* 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 <cassert>
#import <React/RCTBridge+Private.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxModule.h>
#import <React/RCTLog.h>
#import <jsireact/TurboCxxModule.h>
#import <jsireact/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::JSCallInvoker> _jsInvoker;
std::shared_ptr<react::TurboModuleBinding> _binding;
__weak id<RCTTurboModuleManagerDelegate> _delegate;
__weak RCTBridge *_bridge;
/**
* TODO(rsnara):
* 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;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge delegate:(id<RCTTurboModuleManagerDelegate>)delegate
{
if (self = [super init]) {
_jsInvoker = std::make_shared<react::JSCallInvoker>(bridge.reactInstance);
_delegate = delegate;
_bridge = bridge;
// Necessary to allow NativeModules to lookup TurboModules
[bridge setRCTTurboModuleLookupDelegate:self];
__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;
/**
* By default, all TurboModules are long-lived.
* Additionally, if a TurboModule with the name `name` isn't found, then we
* trigger an assertion failure.
*/
return [strongSelf provideTurboModule: name.c_str()];
};
_binding = std::make_shared<react::TurboModuleBinding>(moduleProvider);
}
return self;
}
/**
* 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
{
auto rctTurboModuleCacheLookup = _rctTurboModuleCache.find(moduleName);
if (rctTurboModuleCacheLookup != _rctTurboModuleCache.end()) {
return rctTurboModuleCacheLookup->second;
}
/**
* Step 2a: Resolve platform-specific class.
*/
Class moduleClass;
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
*/
id<RCTTurboModule> module = nil;
if ([_delegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) {
module = [_delegate getModuleInstanceFromClass:moduleClass];
} else {
module = [moduleClass new];
}
/**
* 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 {
/**
* 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)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));
}
}
if ([module respondsToSelector:@selector(setTurboModuleLookupDelegate:)]) {
[module setTurboModuleLookupDelegate:self];
}
_rctTurboModuleCache.insert({moduleName, module});
/**
* 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.parentBridge
userInfo:@{@"module": module, @"bridge": RCTNullIfNil(_bridge.parentBridge)}];
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
{
return _rctTurboModuleCache.find(std::string(moduleName)) != _rctTurboModuleCache.end();
}
@end