diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index 8a6ae4212b54c3..90dc1efc07d7c6 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -33,6 +33,10 @@ } \ } +static const NSUInteger kNameIndex = 0; +static const NSUInteger kConstantsIndex = 1; +static const NSUInteger kMethodsIndex = 2; + @interface TestExecutor : NSObject @property (nonatomic, readonly, copy) NSMutableDictionary *injectedStuff; @@ -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; } }]; @@ -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; } }]; @@ -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 @@ -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; } }]; @@ -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); diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index d79be798f8362a..2302466220b595 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -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]]; } } @@ -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]; @@ -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 @@ -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 diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h index f69e2beec57461..249d198dd98e58 100644 --- a/React/Base/RCTBridge+Private.h +++ b/React/Base/RCTBridge+Private.h @@ -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 *)buffer; - /** * Exposed for the RCTJSCExecutor for sending native methods called from * JavaScript in the middle of a batch. */ - (void)handleBuffer:(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 */ diff --git a/React/Base/RCTBridgeMethod.h b/React/Base/RCTBridgeMethod.h index bfa466cef4ef0f..657e80a86c0a5b 100644 --- a/React/Base/RCTBridgeMethod.h +++ b/React/Base/RCTBridgeMethod.h @@ -14,6 +14,7 @@ typedef NS_ENUM(NSUInteger, RCTFunctionType) { RCTFunctionTypeNormal, RCTFunctionTypePromise, + RCTFunctionTypeSync, }; @protocol RCTBridgeMethod @@ -21,8 +22,8 @@ typedef NS_ENUM(NSUInteger, RCTFunctionType) { @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 diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm index 06d572026f43a8..e92aff44295637 100644 --- a/React/Base/RCTModuleData.mm +++ b/React/Base/RCTModuleData.mm @@ -245,8 +245,7 @@ - (NSString *)name NSMutableArray> *moduleMethods = [NSMutableArray new]; if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) { - [self instance]; - [moduleMethods addObjectsFromArray:[_instance methodsToExport]]; + [moduleMethods addObjectsFromArray:[self.instance methodsToExport]]; } unsigned int methodCount; @@ -310,29 +309,33 @@ - (NSArray *)config RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil); NSMutableArray *methods = self.methods.count ? [NSMutableArray new] : nil; - NSMutableArray *asyncMethods = nil; + NSMutableArray *promiseMethods = nil; + NSMutableArray *syncMethods = nil; + for (id 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; } diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index 8c51824e9b704c..1395bc9c46f613 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -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]; @@ -459,7 +459,7 @@ - (void)invokeWithBridge:(RCTBridge *)bridge Updating both should make this error go away.", RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, actualCount, expectedCount); - return; + return nil; } } @@ -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++; } @@ -497,6 +497,8 @@ - (void)invokeWithBridge:(RCTBridge *)bridge } } } + + return nil; } - (NSString *)methodName diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index af1aa8e05e316f..30f2f4ae315d4e 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -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); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 9f456d47dd8105..b16503a8fe188d 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -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; diff --git a/React/Executors/RCTJSCExecutor.mm b/React/Executors/RCTJSCExecutor.mm index b0a61b51658a6c..eb94cb8480af07 100644 --- a/React/Executors/RCTJSCExecutor.mm +++ b/React/Executors/RCTJSCExecutor.mm @@ -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) {