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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
@class RCTModuleData; @class RCTModuleData;
@class RCTPerformanceLogger; @class RCTPerformanceLogger;
@protocol RCTJavaScriptExecutor;
@interface RCTBridge () @interface RCTBridge ()
{ {
@ -67,7 +68,9 @@
@interface RCTBridge (RCTBatchedBridge) @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; @property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;

View File

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

View File

@ -15,7 +15,7 @@
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); 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 * Abstracts away a JavaScript execution context - we may be running code in a

View File

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

View File

@ -11,6 +11,8 @@
#import "RCTJavaScriptExecutor.h" #import "RCTJavaScriptExecutor.h"
typedef void (^RCTJavaScriptValueCallback)(JSValue *result, NSError *error);
/** /**
* Default name for the JS thread * Default name for the JS thread
*/ */
@ -75,4 +77,15 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
JSContext:(JSContext **)JSContext JSContext:(JSContext **)JSContext
error:(NSError **)error; 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 @end

View File

@ -595,16 +595,29 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete - (void)flushedQueue:(RCTJavaScriptCallback)onComplete
{ {
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 // 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 - (void)_callFunctionOnModule:(NSString *)module
method:(NSString *)method method:(NSString *)method
arguments:(NSArray *)args arguments:(NSArray *)args
callback:(RCTJavaScriptCallback)onComplete flushQueue:(BOOL)flushQueue
unwrapResult:(BOOL)unwrapResult
callback:(RCTJavaScriptCallback)onComplete
{ {
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 // 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 - (void)invokeCallbackID:(NSNumber *)cbID
@ -612,11 +625,12 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
callback:(RCTJavaScriptCallback)onComplete callback:(RCTJavaScriptCallback)onComplete
{ {
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 // 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 - (void)_executeJSCall:(NSString *)method
arguments:(NSArray *)arguments arguments:(NSArray *)arguments
unwrapResult:(BOOL)unwrapResult
callback:(RCTJavaScriptCallback)onComplete callback:(RCTJavaScriptCallback)onComplete
{ {
RCTAssert(onComplete != nil, @"onComplete block should not be nil"); RCTAssert(onComplete != nil, @"onComplete block should not be nil");
@ -673,20 +687,16 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
error = RCTNSErrorFromJSError(jscWrapper, contextJSRef, errorJSRef); error = RCTNSErrorFromJSError(jscWrapper, contextJSRef, errorJSRef);
} }
onComplete(nil, error); 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}))]; }), 0, @"js_call", (@{@"method": method, @"args": arguments}))];
} }