Support sync method calls in the objc bridge

Reviewed By: mhorowitz

Differential Revision: D3801188

fbshipit-source-id: b990680049a46840472a25e66882f8a29890ae90
This commit is contained in:
Pieter De Baets 2016-09-05 07:32:20 -07:00 committed by Facebook Github Bot 2
parent 753b37e479
commit dda3c5f48d
9 changed files with 75 additions and 69 deletions

View File

@ -33,6 +33,10 @@
} \ } \
} }
static const NSUInteger kNameIndex = 0;
static const NSUInteger kConstantsIndex = 1;
static const NSUInteger kMethodsIndex = 2;
@interface TestExecutor : NSObject <RCTJavaScriptExecutor> @interface TestExecutor : NSObject <RCTJavaScriptExecutor>
@property (nonatomic, readonly, copy) NSMutableDictionary<NSString *, id> *injectedStuff; @property (nonatomic, readonly, copy) NSMutableDictionary<NSString *, id> *injectedStuff;
@ -195,10 +199,10 @@ RCT_EXPORT_MODULE(TestModule)
NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, BOOL *stop) { [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); testModuleID = @(i);
testConstants = moduleConfig[1]; testConstants = moduleConfig[kConstantsIndex];
testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]); testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]);
*stop = YES; *stop = YES;
} }
}]; }];
@ -221,9 +225,9 @@ RCT_EXPORT_MODULE(TestModule)
NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) { [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); testModuleID = @(i);
testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]); testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]);
*stop = YES; *stop = YES;
} }
}]; }];
@ -234,7 +238,7 @@ RCT_EXPORT_MODULE(TestModule)
NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42]; NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]]; NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]];
[_bridge.batchedBridge handleBuffer:buffer]; [_bridge.batchedBridge handleBuffer:buffer batchEnded:YES];
dispatch_sync(_methodQueue, ^{ dispatch_sync(_methodQueue, ^{
// clear the queue // clear the queue
@ -253,9 +257,9 @@ RCT_EXPORT_MODULE(TestModule)
NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) { [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); testModuleID = @(i);
testMethodID = @([moduleConfig[1] indexOfObject:@"testMethod"]); testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]);
*stop = YES; *stop = YES;
} }
}]; }];
@ -266,7 +270,7 @@ RCT_EXPORT_MODULE(TestModule)
NSArray *args = @[]; NSArray *args = @[];
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]]; NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]];
[_bridge.batchedBridge handleBuffer:buffer]; [_bridge.batchedBridge handleBuffer:buffer batchEnded:YES];
dispatch_sync(_unregisteredTestModule.methodQueue, ^{ dispatch_sync(_unregisteredTestModule.methodQueue, ^{
XCTAssertTrue(self->_unregisteredTestModule.testMethodCalled); XCTAssertTrue(self->_unregisteredTestModule.testMethodCalled);

View File

@ -972,10 +972,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
[self.flowIDMapLock unlock]; [self.flowIDMapLock unlock];
} }
#endif #endif
[self _handleRequestNumber:index [self callNativeModule:[moduleIDs[index] integerValue]
moduleID:[moduleIDs[index] integerValue] method:[methodIDs[index] integerValue]
methodID:[methodIDs[index] integerValue] params:paramsArrays[index]];
params:paramsArrays[index]];
} }
} }
@ -1013,18 +1012,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
} }
} }
- (BOOL)_handleRequestNumber:(NSUInteger)i - (id)callNativeModule:(NSUInteger)moduleID
moduleID:(NSUInteger)moduleID method:(NSUInteger)methodID
methodID:(NSUInteger)methodID params:(NSArray *)params
params:(NSArray *)params
{ {
if (!_valid) { if (!_valid) {
return NO; return nil;
}
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
} }
RCTModuleData *moduleData = _moduleDataByID[moduleID]; RCTModuleData *moduleData = _moduleDataByID[moduleID];
@ -1040,7 +1033,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
} }
@try { @try {
[method invokeWithBridge:self module:moduleData.instance arguments:params]; return [method invokeWithBridge:self module:moduleData.instance arguments:params];
} }
@catch (NSException *exception) { @catch (NSException *exception) {
// Pass on JS exceptions // Pass on JS exceptions
@ -1052,9 +1045,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", @"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
exception, method.JSMethodName, moduleData.name, params]; exception, method.JSMethodName, moduleData.name, params];
RCTFatal(RCTErrorWithMessage(message)); RCTFatal(RCTErrorWithMessage(message));
return nil;
} }
return YES;
} }
- (void)startProfiling - (void)startProfiling

View File

@ -99,17 +99,19 @@
- (void)startProfiling; - (void)startProfiling;
- (void)stopProfiling:(void (^)(NSData *))callback; - (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 * Exposed for the RCTJSCExecutor for sending native methods called from
* JavaScript in the middle of a batch. * JavaScript in the middle of a batch.
*/ */
- (void)handleBuffer:(NSArray<NSArray *> *)buffer batchEnded:(BOOL)hasEnded; - (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 * Exposed for the RCTJSCExecutor for lazily loading native modules
*/ */

View File

@ -14,6 +14,7 @@
typedef NS_ENUM(NSUInteger, RCTFunctionType) { typedef NS_ENUM(NSUInteger, RCTFunctionType) {
RCTFunctionTypeNormal, RCTFunctionTypeNormal,
RCTFunctionTypePromise, RCTFunctionTypePromise,
RCTFunctionTypeSync,
}; };
@protocol RCTBridgeMethod <NSObject> @protocol RCTBridgeMethod <NSObject>
@ -21,8 +22,8 @@ typedef NS_ENUM(NSUInteger, RCTFunctionType) {
@property (nonatomic, copy, readonly) NSString *JSMethodName; @property (nonatomic, copy, readonly) NSString *JSMethodName;
@property (nonatomic, readonly) RCTFunctionType functionType; @property (nonatomic, readonly) RCTFunctionType functionType;
- (void)invokeWithBridge:(RCTBridge *)bridge - (id)invokeWithBridge:(RCTBridge *)bridge
module:(id)module module:(id)module
arguments:(NSArray *)arguments; arguments:(NSArray *)arguments;
@end @end

View File

@ -245,8 +245,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new]; NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) { if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
[self instance]; [moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
[moduleMethods addObjectsFromArray:[_instance methodsToExport]];
} }
unsigned int methodCount; unsigned int methodCount;
@ -310,29 +309,33 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
NSMutableArray<NSString *> *methods = self.methods.count ? [NSMutableArray new] : 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) { for (id<RCTBridgeMethod> method in self.methods) {
if (method.functionType == RCTFunctionTypePromise) { if (method.functionType == RCTFunctionTypePromise) {
if (!asyncMethods) { if (!promiseMethods) {
asyncMethods = [NSMutableArray new]; promiseMethods = [NSMutableArray new];
} }
[asyncMethods addObject:@(methods.count)]; [promiseMethods addObject:@(methods.count)];
}
else if (method.functionType == RCTFunctionTypeSync) {
if (!syncMethods) {
syncMethods = [NSMutableArray new];
}
[syncMethods addObject:@(methods.count)];
} }
[methods addObject:method.JSMethodName]; [methods addObject:method.JSMethodName];
} }
NSMutableArray *config = [NSMutableArray new]; NSArray *config = @[
[config addObject:self.name]; self.name,
if (constants.count) { RCTNullIfNil(constants),
[config addObject:constants]; RCTNullIfNil(methods),
} RCTNullIfNil(promiseMethods),
if (methods) { RCTNullIfNil(syncMethods)
[config addObject:methods]; ];
if (asyncMethods) { RCT_PROFILE_END_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass]), nil);
[config addObject:asyncMethods];
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
return config; return config;
} }

View File

@ -429,9 +429,9 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument
} }
} }
- (void)invokeWithBridge:(RCTBridge *)bridge - (id)invokeWithBridge:(RCTBridge *)bridge
module:(id)module module:(id)module
arguments:(NSArray *)arguments arguments:(NSArray *)arguments
{ {
if (_argumentBlocks == nil) { if (_argumentBlocks == nil) {
[self processMethodSignature]; [self processMethodSignature];
@ -459,7 +459,7 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument
Updating both should make this error go away.", Updating both should make this error go away.",
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
actualCount, expectedCount); actualCount, expectedCount);
return; return nil;
} }
} }
@ -471,7 +471,7 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument
// Invalid argument, abort // Invalid argument, abort
RCTLogArgumentError(self, index, json, RCTLogArgumentError(self, index, json,
"could not be processed. Aborting method call."); "could not be processed. Aborting method call.");
return; return nil;
} }
index++; index++;
} }
@ -497,6 +497,8 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument
} }
} }
} }
return nil;
} }
- (NSString *)methodName - (NSString *)methodName

View File

@ -104,8 +104,8 @@ RCT_EXTERN UIAlertView *__nullable RCTAlertView(NSString *title,
RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message); RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
// Convert nil values to NSNull, and vice-versa // Convert nil values to NSNull, and vice-versa
RCT_EXTERN id __nullable RCTNilIfNull(id __nullable value); #define RCTNullIfNil(value) (value ?: (id)kCFNull)
RCT_EXTERN id RCTNullIfNil(id __nullable value); #define RCTNilIfNull(value) (value == (id)kCFNull ? nil : value)
// Convert NaN or infinite values to zero, as these aren't JSON-safe // Convert NaN or infinite values to zero, as these aren't JSON-safe
RCT_EXTERN double RCTZeroIfNaN(double value); RCT_EXTERN double RCTZeroIfNaN(double value);

View File

@ -522,16 +522,6 @@ NSError *RCTErrorWithMessage(NSString *message)
return [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; 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) double RCTZeroIfNaN(double value)
{ {
return isnan(value) || isinf(value) ? 0 : value; return isnan(value) || isinf(value) ? 0 : value;

View File

@ -447,6 +447,18 @@ static NSThread *newJavaScriptThread(void)
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil); 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 #if RCT_PROFILE
__weak RCTBridge *weakBridge = self->_bridge; __weak RCTBridge *weakBridge = self->_bridge;
context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) { context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {