[ReactNative] Fix bridge event dedupe

This commit is contained in:
Tadeu Zagallo
2015-04-28 08:02:56 -07:00
parent f7276b0ae4
commit 2f4430cf51
3 changed files with 113 additions and 35 deletions

View File

@@ -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

View File

@@ -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";

View File

@@ -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