Add MessageQueue method for executing function and returning its result

Reviewed By: majak

Differential Revision: D3175793

fbshipit-source-id: e1e66e3dcde8b1fb35973340e12d947a0e955775
This commit is contained in:
Pieter De Baets 2016-07-18 07:12:19 -07:00 committed by Facebook Github Bot 1
parent ca5d1aebab
commit 7fa677f7c3
14 changed files with 106 additions and 76 deletions

View File

@ -38,11 +38,10 @@
if (getenv("CI_USE_PACKAGER")) {
NSString *app = @"IntegrationTests/IntegrationTestsApp";
scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]];
RCTAssert(scriptURL != nil, @"No scriptURL set");
} else {
scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
RCTAssert(scriptURL != nil, @"Could not locate main.jsBundle");
}
RCTAssert(scriptURL != nil, @"No scriptURL set");
_bridge = [[RCTBridge alloc] initWithBundleURL:scriptURL moduleProvider:NULL launchOptions:nil];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:60];

View File

@ -21,6 +21,7 @@
#import "RCTUtils.h"
#import "RCTUIManager.h"
#import "RCTViewManager.h"
#import "RCTJavaScriptExecutor.h"
#define RUN_RUNLOOP_WHILE(CONDITION) \
{ \

View File

@ -19,6 +19,7 @@
#import "RCTBridge+Private.h"
#import "RCTBridgeModule.h"
#import "RCTUtils.h"
#import "RCTJavaScriptExecutor.h"
#define RUN_RUNLOOP_WHILE(CONDITION) \
{ \

View File

@ -9,10 +9,12 @@
#import "RCTImageLoader.h"
#import <libkern/OSAtomic.h>
#import <UIKit/UIKit.h>
#import <ImageIO/ImageIO.h>
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
#import "RCTConvert.h"
#import "RCTDefines.h"
#import "RCTImageUtils.h"

View File

@ -47,11 +47,10 @@ static const NSTimeInterval kTestTeardownTimeoutSeconds = 30;
if (getenv("CI_USE_PACKAGER")) {
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]];
RCTAssert(_scriptURL != nil, @"No scriptURL set");
} else {
_scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle");
}
RCTAssert(_scriptURL != nil, @"No scriptURL set");
}
return self;
}

View File

@ -68,6 +68,7 @@ class MessageQueue {
[
'invokeCallbackAndReturnFlushedQueue',
'callFunctionReturnFlushedQueue',
'callFunction',
'flushedQueue',
].forEach((fn) => (this[fn] = this[fn].bind(this)));
@ -98,6 +99,16 @@ class MessageQueue {
return this.flushedQueue();
}
callFunction(module, method, args) {
let result;
guard(() => {
result = this.__callFunction(module, method, args);
this.__callImmediates();
});
return result;
}
invokeCallbackAndReturnFlushedQueue(cbID, args) {
guard(() => {
this.__invokeCallback(cbID, args);
@ -190,8 +201,9 @@ class MessageQueue {
'Module %s is not a registered callable module.',
module
);
moduleMethods[method].apply(moduleMethods, args);
const result = moduleMethods[method].apply(moduleMethods, args);
Systrace.endEvent();
return result;
}
__invokeCallback(cbID, args) {

View File

@ -9,6 +9,8 @@
#import "RCTWebSocketModule.h"
#import <objc/runtime.h>
#import "RCTConvert.h"
#import "RCTUtils.h"

View File

@ -589,7 +589,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
queue:(dispatch_queue_t)queue
{
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
__weak __typeof(self) weakSelf = self;
RCTProfileBeginFlowEvent();
RCTAssert(_javaScriptExecutor != nil, @"Need JS executor to schedule JS work");
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (strongSelf.loading) {
[strongSelf->_pendingCalls addObject:block];
} else {
block();
}
}];
} else if (queue) {
dispatch_async(queue, block);
}
@ -680,32 +697,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
*/
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTBatchedBridge enqueueJSCall:]", nil);
if (!_valid) {
return;
}
NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];
NSString *module = ids[0];
NSString *method = ids[1];
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.valid) {
return;
}
if (strongSelf.loading) {
dispatch_block_t pendingCall = ^{
[weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
};
[strongSelf->_pendingCalls addObject:pendingCall];
} else {
[strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
}
}];
__weak __typeof(self) weakSelf = self;
[self dispatchBlock:^{
[weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
} queue:RCTJSThread];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"", nil);
}
@ -718,27 +722,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
/**
* AnyThread
*/
if (!_valid) {
return;
}
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.valid) {
return;
}
if (strongSelf.loading) {
dispatch_block_t pendingCall = ^{
[weakSelf _actuallyInvokeCallback:cbID arguments:args ?: @[]];
};
[strongSelf->_pendingCalls addObject:pendingCall];
} else {
[strongSelf _actuallyInvokeCallback:cbID arguments:args];
}
}];
__weak __typeof(self) weakSelf = self;
[self dispatchBlock:^{
[weakSelf _actuallyInvokeCallback:cbID arguments:args];
} queue:RCTJSThread];
}
/**
@ -922,11 +913,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
});
};
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else if (queue) {
dispatch_async(queue, block);
}
[self dispatchBlock:block queue:queue];
}
_flowID = callID;

View File

@ -11,6 +11,7 @@
@class RCTModuleData;
@class RCTPerformanceLogger;
@protocol RCTJavaScriptExecutor;
@interface RCTBridge ()
{
@ -67,7 +68,9 @@
@interface RCTBridge (RCTBatchedBridge)
/**
* Used for unit testing, to detect when executor has been invalidated.
* Access the underlying JavaScript executor. You can use this in unit tests to detect
* when the executor has been invalidated, or when you want to schedule calls on the
* JS VM outside of React Native. Use with care!
*/
@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;

View File

@ -14,7 +14,6 @@
#import "RCTDefines.h"
#import "RCTFrameUpdate.h"
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"
@class RCTBridge;
@class RCTEventDispatcher;

View File

@ -15,7 +15,7 @@
#import "RCTInvalidating.h"
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
typedef void (^RCTJavaScriptCallback)(id result, NSError *error);
/**
* Abstracts away a JavaScript execution context - we may be running code in a

View File

@ -9,6 +9,8 @@
#import "RCTModuleData.h"
#import <objc/runtime.h>
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTModuleMethod.h"

View File

@ -11,6 +11,8 @@
#import "RCTJavaScriptExecutor.h"
typedef void (^RCTJavaScriptValueCallback)(JSValue *result, NSError *error);
/**
* Default name for the JS thread
*/
@ -75,4 +77,15 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
JSContext:(JSContext **)JSContext
error:(NSError **)error;
/**
* Invokes the given module/method directly. The completion block will be called with the
* JSValue returned by the JS context.
*
* Currently this does not flush the JS-to-native message queue.
*/
- (void)callFunctionOnModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
jsValueCallback:(RCTJavaScriptValueCallback)onComplete;
@end

View File

@ -595,16 +595,29 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
{
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
[self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete];
[self _executeJSCall:@"flushedQueue" arguments:@[] unwrapResult:YES callback:onComplete];
}
- (void)callFunctionOnModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
callback:(RCTJavaScriptCallback)onComplete
- (void)_callFunctionOnModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
flushQueue:(BOOL)flushQueue
unwrapResult:(BOOL)unwrapResult
callback:(RCTJavaScriptCallback)onComplete
{
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
[self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];
NSString *bridgeMethod = flushQueue ? @"callFunctionReturnFlushedQueue" : @"callFunction";
[self _executeJSCall:bridgeMethod arguments:@[module, method, args] unwrapResult:unwrapResult callback:onComplete];
}
- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args callback:(RCTJavaScriptCallback)onComplete
{
[self _callFunctionOnModule:module method:method arguments:args flushQueue:YES unwrapResult:YES callback:onComplete];
}
- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args jsValueCallback:(RCTJavaScriptValueCallback)onComplete
{
[self _callFunctionOnModule:module method:method arguments:args flushQueue:NO unwrapResult:NO callback:onComplete];
}
- (void)invokeCallbackID:(NSNumber *)cbID
@ -612,11 +625,12 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
callback:(RCTJavaScriptCallback)onComplete
{
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
[self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete];
[self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] unwrapResult:YES callback:onComplete];
}
- (void)_executeJSCall:(NSString *)method
arguments:(NSArray *)arguments
unwrapResult:(BOOL)unwrapResult
callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
@ -673,20 +687,16 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
error = RCTNSErrorFromJSError(jscWrapper, contextJSRef, errorJSRef);
}
onComplete(nil, error);
return;
} else {
id objcValue = nil;
// We often return `null` from JS when there is nothing for native side. [JSValue toValue]
// returns [NSNull null] in this case, which we don't want.
if (!jscWrapper->JSValueIsNull(contextJSRef, resultJSRef)) {
JSValue *result = [jscWrapper->JSValue valueWithJSValueRef:resultJSRef inContext:context];
objcValue = unwrapResult ? [result toObject] : result;
}
onComplete(objcValue, nil);
}
// Looks like making lots of JSC API calls is slower than communicating by using a JSON
// string. Also it ensures that data stuctures don't have cycles and non-serializable fields.
// see [RCTJSCExecutorTests testDeserializationPerf]
id objcValue;
// We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
// to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
if (!jscWrapper->JSValueIsNull(contextJSRef, resultJSRef)) {
objcValue = [[jscWrapper->JSValue valueWithJSValueRef:resultJSRef inContext:context] toObject];
}
onComplete(objcValue, nil);
}), 0, @"js_call", (@{@"method": method, @"args": arguments}))];
}