Skip to content

Commit

Permalink
Make TurboModules dispatch method calls via native CallInvoker
Browse files Browse the repository at this point in the history
Summary:
This diff:
1. Has ObjC NativeModules use the native `CallInvoker` to invoke JS -> native sync/async calls.
2. Integrates the native `CallInvoker` for each ObjC NativeModule with the bridge. This way, the bridge is informed of all JS -> native TurboModule method calls, and dispatches `onBatchComplete` appropriately.

Changelog:
[iOS][Fixed] Integrate ObjC TurboModules async method calls with the bridge

Reviewed By: fkgozali

Differential Revision: D20831545

fbshipit-source-id: da1cbb4ecef4cae85841ca7ef625ab8e380760cd
  • Loading branch information
RSNara authored and sasurau4 committed Apr 15, 2020
1 parent 5c5c574 commit 148b8f9
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 37 deletions.
1 change: 1 addition & 0 deletions ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule {
size_t count);

id<RCTTurboModule> instance_;
std::shared_ptr<CallInvoker> nativeInvoker_;

protected:
void setMethodArgConversionSelector(NSString *methodName, int argIndex, NSString *fnName);
Expand Down
37 changes: 4 additions & 33 deletions ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -360,39 +360,10 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback(
[performanceLogger syncRCTTurboModuleMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId];
};

// Backward-compatibility layer for calling module methods on specific queue.
dispatch_queue_t methodQueue = NULL;
if ([instance_ conformsToProtocol:@protocol(RCTBridgeModule)] &&
[instance_ respondsToSelector:@selector(methodQueue)]) {
methodQueue = [instance_ performSelector:@selector(methodQueue)];
}

if (methodQueue == NULL || methodQueue == RCTJSThread) {
// This is the default mode of execution: on JS thread.
block();
} else if (methodQueue == dispatch_get_main_queue()) {
if (returnType == VoidKind) {
// Void methods are treated as async for now, so there's no need to block here.

[performanceLogger_ asyncRCTTurboModuleMethodCallDispatch:moduleName
methodName:methodName
methodCallId:methodCallId];
RCTExecuteOnMainQueue(block);
} else {
// This is not ideal, but provides the simplest mechanism for now.
// Eventually, methods should be responsible to queue things up to different queue if they need to.
// TODO: consider adding timer to warn if this method invocation takes too long.
RCTUnsafeExecuteOnMainQueueSync(block);
}
if (returnType == VoidKind) {
nativeInvoker_->invokeAsync([block]() -> void { block(); });
} else {
if (returnType == VoidKind) {
[performanceLogger_ asyncRCTTurboModuleMethodCallDispatch:moduleName
methodName:methodName
methodCallId:methodCallId];
dispatch_async(methodQueue, block);
} else {
dispatch_sync(methodQueue, block);
}
nativeInvoker_->invokeSync([block]() -> void { block(); });
}

// VoidKind can't be null
Expand Down Expand Up @@ -640,7 +611,7 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback(
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<CallInvoker> nativeInvoker,
id<RCTTurboModulePerformanceLogger> perfLogger)
: TurboModule(name, jsInvoker), instance_(instance), performanceLogger_(perfLogger)
: TurboModule(name, jsInvoker), instance_(instance), nativeInvoker_(nativeInvoker), performanceLogger_(perfLogger)
{
}

Expand Down
59 changes: 55 additions & 4 deletions ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@
*/
static char kAssociatedMethodQueueKey;

namespace {
class MethodQueueNativeCallInvoker : public facebook::react::CallInvoker {
private:
dispatch_queue_t methodQueue_;

public:
MethodQueueNativeCallInvoker(dispatch_queue_t methodQueue) : methodQueue_(methodQueue) {}
void invokeAsync(std::function<void()> &&work) override
{
if (methodQueue_ == RCTJSThread) {
work();
return;
}

__block auto retainedWork = std::move(work);
dispatch_async(methodQueue_, ^{
retainedWork();
});
}

void invokeSync(std::function<void()> &&work) override
{
if (methodQueue_ == RCTJSThread) {
work();
return;
}

__block auto retainedWork = std::move(work);
dispatch_sync(methodQueue_, ^{
retainedWork();
});
}
};
}

// 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)
Expand Down Expand Up @@ -164,12 +199,28 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name

Class moduleClass = [module class];

dispatch_queue_t methodQueue = (dispatch_queue_t)objc_getAssociatedObject(module, &kAssociatedMethodQueueKey);

/**
* Step 2c: Create and native CallInvoker from the TurboModule's method queue.
*/
std::shared_ptr<facebook::react::CallInvoker> nativeInvoker =
std::make_shared<MethodQueueNativeCallInvoker>(methodQueue);

/**
* Have RCTCxxBridge decorate native CallInvoker, so that it's aware of TurboModule async method calls.
* This helps the bridge fire onBatchComplete as readily as it should.
*/
if ([_bridge respondsToSelector:@selector(decorateNativeCallInvoker:)]) {
nativeInvoker = [_bridge decorateNativeCallInvoker:nativeInvoker];
}

// If RCTTurboModule supports creating its own C++ TurboModule object,
// allow it to do so.
if ([module respondsToSelector:@selector(getTurboModuleWithJsInvoker:nativeInvoker:perfLogger:)]) {
[_performanceLogger getTurboModuleFromRCTTurboModuleStart:moduleName];
auto turboModule = [module getTurboModuleWithJsInvoker:_jsInvoker
nativeInvoker:nullptr
nativeInvoker:nativeInvoker
perfLogger:_performanceLogger];
[_performanceLogger getTurboModuleFromRCTTurboModuleEnd:moduleName];
assert(turboModule != nullptr);
Expand All @@ -178,7 +229,7 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name
}

/**
* Step 2c: If the moduleClass is a legacy CxxModule, return a TurboCxxModule instance that
* Step 2d: If the moduleClass is a legacy CxxModule, return a TurboCxxModule instance that
* wraps CxxModule.
*/
if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) {
Expand All @@ -192,13 +243,13 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name
}

/**
* Step 2d: Return an exact sub-class of ObjC TurboModule
* Step 2e: Return an exact sub-class of ObjC TurboModule
*/
[_performanceLogger getTurboModuleFromTMMDelegateStart:moduleName];
auto turboModule = [_delegate getTurboModule:moduleName
instance:module
jsInvoker:_jsInvoker
nativeInvoker:nullptr
nativeInvoker:nativeInvoker
perfLogger:_performanceLogger];
[_performanceLogger getTurboModuleFromTMMDelegateEnd:moduleName];
if (turboModule != nullptr) {
Expand Down

0 comments on commit 148b8f9

Please sign in to comment.