Skip to content

Commit

Permalink
Support sync method calls in the objc bridge
Browse files Browse the repository at this point in the history
Reviewed By: mhorowitz

Differential Revision: D3801188

fbshipit-source-id: b990680049a46840472a25e66882f8a29890ae90
  • Loading branch information
javache authored and Facebook Github Bot 2 committed Sep 5, 2016
1 parent 753b37e commit dda3c5f
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 69 deletions.
22 changes: 13 additions & 9 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
Expand Up @@ -33,6 +33,10 @@
} \
}

static const NSUInteger kNameIndex = 0;
static const NSUInteger kConstantsIndex = 1;
static const NSUInteger kMethodsIndex = 2;

@interface TestExecutor : NSObject <RCTJavaScriptExecutor>

@property (nonatomic, readonly, copy) NSMutableDictionary<NSString *, id> *injectedStuff;
Expand Down Expand Up @@ -195,10 +199,10 @@ - (void)testHookRegistration

NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, BOOL *stop) {
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[kNameIndex] isEqualToString:@"TestModule"]) {
testModuleID = @(i);
testConstants = moduleConfig[1];
testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
testConstants = moduleConfig[kConstantsIndex];
testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]);
*stop = YES;
}
}];
Expand All @@ -221,9 +225,9 @@ - (void)testCallNativeMethod

NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) {
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[kNameIndex] isEqualToString:@"TestModule"]) {
testModuleID = @(i);
testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]);
*stop = YES;
}
}];
Expand All @@ -234,7 +238,7 @@ - (void)testCallNativeMethod
NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]];

[_bridge.batchedBridge handleBuffer:buffer];
[_bridge.batchedBridge handleBuffer:buffer batchEnded:YES];

dispatch_sync(_methodQueue, ^{
// clear the queue
Expand All @@ -253,9 +257,9 @@ - (void)testCallUnregisteredModuleMethod

NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) {
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"UnregisteredTestModule"]) {
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[kNameIndex] isEqualToString:@"UnregisteredTestModule"]) {
testModuleID = @(i);
testMethodID = @([moduleConfig[1] indexOfObject:@"testMethod"]);
testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]);
*stop = YES;
}
}];
Expand All @@ -266,7 +270,7 @@ - (void)testCallUnregisteredModuleMethod
NSArray *args = @[];
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]];

[_bridge.batchedBridge handleBuffer:buffer];
[_bridge.batchedBridge handleBuffer:buffer batchEnded:YES];

dispatch_sync(_unregisteredTestModule.methodQueue, ^{
XCTAssertTrue(self->_unregisteredTestModule.testMethodCalled);
Expand Down
26 changes: 9 additions & 17 deletions React/Base/RCTBatchedBridge.m
Expand Up @@ -972,10 +972,9 @@ - (void)handleBuffer:(NSArray *)buffer
[self.flowIDMapLock unlock];
}
#endif
[self _handleRequestNumber:index
moduleID:[moduleIDs[index] integerValue]
methodID:[methodIDs[index] integerValue]
params:paramsArrays[index]];
[self callNativeModule:[moduleIDs[index] integerValue]
method:[methodIDs[index] integerValue]
params:paramsArrays[index]];
}
}

Expand Down Expand Up @@ -1013,18 +1012,12 @@ - (void)batchDidComplete
}
}

- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID
methodID:(NSUInteger)methodID
params:(NSArray *)params
- (id)callNativeModule:(NSUInteger)moduleID
method:(NSUInteger)methodID
params:(NSArray *)params
{
if (!_valid) {
return NO;
}

if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
return nil;
}

RCTModuleData *moduleData = _moduleDataByID[moduleID];
Expand All @@ -1040,7 +1033,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
}

@try {
[method invokeWithBridge:self module:moduleData.instance arguments:params];
return [method invokeWithBridge:self module:moduleData.instance arguments:params];
}
@catch (NSException *exception) {
// Pass on JS exceptions
Expand All @@ -1052,9 +1045,8 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
@"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
exception, method.JSMethodName, moduleData.name, params];
RCTFatal(RCTErrorWithMessage(message));
return nil;
}

return YES;
}

- (void)startProfiling
Expand Down
12 changes: 7 additions & 5 deletions React/Base/RCTBridge+Private.h
Expand Up @@ -99,17 +99,19 @@
- (void)startProfiling;
- (void)stopProfiling:(void (^)(NSData *))callback;

/**
* Executes native calls sent by JavaScript. Exposed for testing purposes only
*/
- (void)handleBuffer:(NSArray<NSArray *> *)buffer;

/**
* Exposed for the RCTJSCExecutor for sending native methods called from
* JavaScript in the middle of a batch.
*/
- (void)handleBuffer:(NSArray<NSArray *> *)buffer batchEnded:(BOOL)hasEnded;

/**
* Synchronously call a specific native module's method and return the result
*/
- (id)callNativeModule:(NSUInteger)moduleID
method:(NSUInteger)methodID
params:(NSArray *)params;

/**
* Exposed for the RCTJSCExecutor for lazily loading native modules
*/
Expand Down
7 changes: 4 additions & 3 deletions React/Base/RCTBridgeMethod.h
Expand Up @@ -14,15 +14,16 @@
typedef NS_ENUM(NSUInteger, RCTFunctionType) {
RCTFunctionTypeNormal,
RCTFunctionTypePromise,
RCTFunctionTypeSync,
};

@protocol RCTBridgeMethod <NSObject>

@property (nonatomic, copy, readonly) NSString *JSMethodName;
@property (nonatomic, readonly) RCTFunctionType functionType;

- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments;
- (id)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments;

@end
39 changes: 21 additions & 18 deletions React/Base/RCTModuleData.mm
Expand Up @@ -245,8 +245,7 @@ - (NSString *)name
NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];

if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
[self instance];
[moduleMethods addObjectsFromArray:[_instance methodsToExport]];
[moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
}

unsigned int methodCount;
Expand Down Expand Up @@ -310,29 +309,33 @@ - (NSArray *)config
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);

NSMutableArray<NSString *> *methods = self.methods.count ? [NSMutableArray new] : nil;
NSMutableArray<NSNumber *> *asyncMethods = nil;
NSMutableArray<NSNumber *> *promiseMethods = nil;
NSMutableArray<NSNumber *> *syncMethods = nil;

for (id<RCTBridgeMethod> method in self.methods) {
if (method.functionType == RCTFunctionTypePromise) {
if (!asyncMethods) {
asyncMethods = [NSMutableArray new];
if (!promiseMethods) {
promiseMethods = [NSMutableArray new];
}
[promiseMethods addObject:@(methods.count)];
}
else if (method.functionType == RCTFunctionTypeSync) {
if (!syncMethods) {
syncMethods = [NSMutableArray new];
}
[asyncMethods addObject:@(methods.count)];
[syncMethods addObject:@(methods.count)];
}
[methods addObject:method.JSMethodName];
}

NSMutableArray *config = [NSMutableArray new];
[config addObject:self.name];
if (constants.count) {
[config addObject:constants];
}
if (methods) {
[config addObject:methods];
if (asyncMethods) {
[config addObject:asyncMethods];
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
NSArray *config = @[
self.name,
RCTNullIfNil(constants),
RCTNullIfNil(methods),
RCTNullIfNil(promiseMethods),
RCTNullIfNil(syncMethods)
];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass]), nil);
return config;
}

Expand Down
12 changes: 7 additions & 5 deletions React/Base/RCTModuleMethod.m
Expand Up @@ -429,9 +429,9 @@ - (RCTFunctionType)functionType
}
}

- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
- (id)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
{
if (_argumentBlocks == nil) {
[self processMethodSignature];
Expand Down Expand Up @@ -459,7 +459,7 @@ - (void)invokeWithBridge:(RCTBridge *)bridge
Updating both should make this error go away.",
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
actualCount, expectedCount);
return;
return nil;
}
}

Expand All @@ -471,7 +471,7 @@ - (void)invokeWithBridge:(RCTBridge *)bridge
// Invalid argument, abort
RCTLogArgumentError(self, index, json,
"could not be processed. Aborting method call.");
return;
return nil;
}
index++;
}
Expand All @@ -497,6 +497,8 @@ - (void)invokeWithBridge:(RCTBridge *)bridge
}
}
}

return nil;
}

- (NSString *)methodName
Expand Down
4 changes: 2 additions & 2 deletions React/Base/RCTUtils.h
Expand Up @@ -104,8 +104,8 @@ RCT_EXTERN UIAlertView *__nullable RCTAlertView(NSString *title,
RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);

// Convert nil values to NSNull, and vice-versa
RCT_EXTERN id __nullable RCTNilIfNull(id __nullable value);
RCT_EXTERN id RCTNullIfNil(id __nullable value);
#define RCTNullIfNil(value) (value ?: (id)kCFNull)
#define RCTNilIfNull(value) (value == (id)kCFNull ? nil : value)

// Convert NaN or infinite values to zero, as these aren't JSON-safe
RCT_EXTERN double RCTZeroIfNaN(double value);
Expand Down
10 changes: 0 additions & 10 deletions React/Base/RCTUtils.m
Expand Up @@ -522,16 +522,6 @@ BOOL RCTForceTouchAvailable(void)
return [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
}

id RCTNullIfNil(id __nullable value)
{
return value ?: (id)kCFNull;
}

id __nullable RCTNilIfNull(id __nullable value)
{
return value == (id)kCFNull ? nil : value;
}

double RCTZeroIfNaN(double value)
{
return isnan(value) || isinf(value) ? 0 : value;
Expand Down
12 changes: 12 additions & 0 deletions React/Executors/RCTJSCExecutor.mm
Expand Up @@ -447,6 +447,18 @@ - (void)setUp
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil);
};

context[@"nativeCallSyncHook"] = ^id(NSUInteger module, NSUInteger method, NSArray *args) {
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid) {
return nil;
}

RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeCallSyncHook", nil);
id result = [strongSelf->_bridge callNativeModule:module method:method params:args];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config", nil);
return result;
};

#if RCT_PROFILE
__weak RCTBridge *weakBridge = self->_bridge;
context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
Expand Down

0 comments on commit dda3c5f

Please sign in to comment.