mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-05 17:27:54 +08:00
[ReactNative] Fix bridge event dedupe
This commit is contained in:
@@ -672,6 +672,8 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
||||
*/
|
||||
static NSMutableDictionary *RCTLocalModuleIDs;
|
||||
static NSMutableDictionary *RCTLocalMethodIDs;
|
||||
static NSMutableArray *RCTLocalModuleNames;
|
||||
static NSMutableArray *RCTLocalMethodNames;
|
||||
static NSDictionary *RCTLocalModulesConfig()
|
||||
{
|
||||
static NSMutableDictionary *localModules;
|
||||
@@ -680,6 +682,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
|
||||
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalModuleNames = [[NSMutableArray alloc] init];
|
||||
RCTLocalMethodNames = [[NSMutableArray alloc] init];
|
||||
|
||||
localModules = [[NSMutableDictionary alloc] init];
|
||||
for (NSString *moduleDotMethod in RCTJSMethods()) {
|
||||
@@ -711,6 +715,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
// Add module and method lookup
|
||||
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
|
||||
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
|
||||
[RCTLocalModuleNames addObject:moduleName];
|
||||
[RCTLocalMethodNames addObject:methodName];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1072,7 +1078,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
#pragma mark - RCTBridge methods
|
||||
|
||||
/**
|
||||
* Like JS::call, for objective-c.
|
||||
* Public. Can be invoked from any thread.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
||||
{
|
||||
@@ -1083,12 +1089,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||
|
||||
if (!_loading) {
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1106,10 +1110,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
if (!_loading) {
|
||||
#if BATCHED_BRIDGE
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
dispatch_block_t block = ^{
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
};
|
||||
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
|
||||
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
|
||||
} else {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -1163,33 +1174,83 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
|
||||
* on the JS thread, but only in non-batched mode.
|
||||
*/
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
if ([module isEqualToString:@"RCTEventEmitter"]) {
|
||||
for (NSDictionary *call in _scheduledCalls) {
|
||||
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
|
||||
[_scheduledCalls removeObject:call];
|
||||
__weak NSMutableArray *weakScheduledCalls = _scheduledCalls;
|
||||
__weak RCTSparseArray *weakScheduledCallbacks = _scheduledCallbacks;
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
NSMutableArray *scheduledCalls = weakScheduledCalls;
|
||||
RCTSparseArray *scheduledCallbacks = weakScheduledCallbacks;
|
||||
if (!scheduledCalls || !scheduledCallbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event deduping
|
||||
*
|
||||
* Right now we make a lot of assumptions about the arguments structure
|
||||
* so just iterate if it's a `callFunctionReturnFlushedQueue()`
|
||||
*/
|
||||
if ([method isEqualToString:@"callFunctionReturnFlushedQueue"]) {
|
||||
NSString *moduleName = RCTLocalModuleNames[[args[0] integerValue]];
|
||||
/**
|
||||
* Keep going if it any event emmiter, e.g. RCT(Device|NativeApp)?EventEmitter
|
||||
*/
|
||||
if ([moduleName hasSuffix:@"EventEmitter"]) {
|
||||
for (NSDictionary *call in [scheduledCalls copy]) {
|
||||
NSArray *callArgs = call[@"args"];
|
||||
/**
|
||||
* If it's the same module && method call on the bridge &&
|
||||
* the same EventEmitter module && method
|
||||
*/
|
||||
if (
|
||||
[call[@"module"] isEqualToString:module] &&
|
||||
[call[@"method"] isEqualToString:method] &&
|
||||
[callArgs[0] isEqual:args[0]] &&
|
||||
[callArgs[1] isEqual:args[1]]
|
||||
) {
|
||||
/**
|
||||
* args[2] contains the actual arguments for the event call, where
|
||||
* args[2][0] is the target for RCTEventEmitter or the eventName
|
||||
* for the other EventEmitters
|
||||
* if RCTEventEmitter we need to compare args[2][1] that will be
|
||||
* the eventName
|
||||
*/
|
||||
if (
|
||||
[args[2][0] isEqual:callArgs[2][0]] &&
|
||||
([moduleName isEqualToString:@"RCTEventEmitter"] ? [args[2][1] isEqual:callArgs[2][1]] : YES)
|
||||
) {
|
||||
[scheduledCalls removeObject:call];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
_scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[_scheduledCalls addObject:call];
|
||||
}
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[scheduledCalls addObject:call];
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
|
||||
@@ -49,6 +49,15 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
||||
*/
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Special case for Timers + ContextExecutor - instead of the default
|
||||
* if jsthread then call else dispatch call on jsthread
|
||||
* ensure the call is made async on the jsthread
|
||||
*/
|
||||
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@end
|
||||
|
||||
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
|
||||
|
||||
@@ -312,12 +312,20 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
/**
|
||||
* Always dispatch async, ensure there are no sync calls on the JS thread
|
||||
* otherwise timers can cause a deadlock
|
||||
*/
|
||||
[self performSelector:@selector(_runBlock:)
|
||||
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
|
||||
if ([NSThread currentThread] != _javaScriptThread) {
|
||||
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
||||
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
||||
onThread:_javaScriptThread
|
||||
withObject:block
|
||||
waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)_runBlock:(dispatch_block_t)block
|
||||
|
||||
Reference in New Issue
Block a user