Support sync method calls in the objc bridge
Reviewed By: mhorowitz Differential Revision: D3801188 fbshipit-source-id: b990680049a46840472a25e66882f8a29890ae90
This commit is contained in:
parent
753b37e479
commit
dda3c5f48d
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue