Permalink
Browse files

Add MessageQueue method for executing function and returning its result

Reviewed By: majak

Differential Revision: D3175793

fbshipit-source-id: e1e66e3dcde8b1fb35973340e12d947a0e955775
  • Loading branch information...
1 parent ca5d1ae commit 7fa677f7c3e1a41ae513abee553940b4632367b8 @javache javache committed with Facebook Github Bot 1 Jul 18, 2016
@@ -38,11 +38,10 @@ - (void)setUp
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];
@@ -21,6 +21,7 @@
#import "RCTUtils.h"
#import "RCTUIManager.h"
#import "RCTViewManager.h"
+#import "RCTJavaScriptExecutor.h"
#define RUN_RUNLOOP_WHILE(CONDITION) \
{ \
@@ -19,6 +19,7 @@
#import "RCTBridge+Private.h"
#import "RCTBridgeModule.h"
#import "RCTUtils.h"
+#import "RCTJavaScriptExecutor.h"
#define RUN_RUNLOOP_WHILE(CONDITION) \
{ \
@@ -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"
@@ -47,11 +47,10 @@ - (instancetype)initWithApp:(NSString *)app
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;
}
@@ -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) {
@@ -9,6 +9,8 @@
#import "RCTWebSocketModule.h"
+#import <objc/runtime.h>
+
#import "RCTConvert.h"
#import "RCTUtils.h"
@@ -589,7 +589,24 @@ - (void)dispatchBlock:(dispatch_block_t)block
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 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
*/
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 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
/**
* 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 @@ - (void)handleBuffer:(NSArray *)buffer
});
};
- if (queue == RCTJSThread) {
- [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
- } else if (queue) {
- dispatch_async(queue, block);
- }
+ [self dispatchBlock:block queue:queue];
}
_flowID = callID;
@@ -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;
@@ -14,7 +14,6 @@
#import "RCTDefines.h"
#import "RCTFrameUpdate.h"
#import "RCTInvalidating.h"
-#import "RCTJavaScriptExecutor.h"
@class RCTBridge;
@class RCTEventDispatcher;
@@ -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
@@ -9,6 +9,8 @@
#import "RCTModuleData.h"
+#import <objc/runtime.h>
+
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTModuleMethod.h"
@@ -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
@@ -595,28 +595,42 @@ - (void)dealloc
- (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
arguments:(NSArray *)args
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 @@ - (void)_executeJSCall:(NSString *)method
error = RCTNSErrorFromJSError(jscWrapper, contextJSRef, errorJSRef);
}
onComplete(nil, error);
- return;
- }
-
- // 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];
+ } 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);
}
-
- onComplete(objcValue, nil);
}), 0, @"js_call", (@{@"method": method, @"args": arguments}))];
}

0 comments on commit 7fa677f

Please sign in to comment.