[ReactNative] Add completionBlock to -[RCTBridge enqueueJSCall:args:]

Summary:
@public

Allow to pass an optional completion block to the bridge JS calls.

The block will be called from the JS thread, after the javascript has finished
running and the returned calls have been processed/dispatched to the native modules.

Test Plan: Added `testCallbackIsCalledOnTheRightTime` to `RKBatchedBridgeTests`
This commit is contained in:
Tadeu Zagallo
2015-05-28 08:29:19 -07:00
parent ebae151f24
commit 3b24f52a20
4 changed files with 105 additions and 15 deletions

View File

@@ -74,6 +74,17 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
/**
* This is the same `enqueueJSCall:args:` but with a handler to notify that JS
* has finished executing the call
*
* NOTE: The `completionHandler` will be called on the *JS* thread, so any expensive
* calls should be avoided here
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod
withArguments:(NSArray *)arguments
completionBlock:(void (^)(void))completionBlock;
/**
* This macro is used to register a JS method to be called via the enqueueJSCall
* bridge method. You should place this macro inside any file that uses the

View File

@@ -214,7 +214,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context;
context:(NSNumber *)context
completionBlock:(void (^)(void))completionBlock;
@end
@@ -227,7 +228,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context;
context:(NSNumber *)context
completionBlock:(void (^)(void))completionBlock;
@end
@@ -346,7 +348,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, args]
context:context];
context:context
completionBlock:nil];
} : ^(NSArray *unused) {});
)
};
@@ -882,10 +885,25 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
[self.batchedBridge enqueueJSCall:moduleDotMethod args:args];
[self.batchedBridge enqueueJSCall:moduleDotMethod
withArguments:args
completionBlock:nil];
}
RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context)
- (void)enqueueJSCall:(NSString *)moduleDotMethod
withArguments:(NSArray *)arguments
completionBlock:(void (^)(void))completionBlock
{
[self.batchedBridge enqueueJSCall:moduleDotMethod
withArguments:arguments
completionBlock:completionBlock];
}
RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context
completionBlock:(void (^)(void))completionBlock)
@end
@@ -1093,6 +1111,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
// Allow testing without a script
dispatch_async(dispatch_get_main_queue(), ^{
_loading = NO;
[_jsDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
@@ -1234,6 +1253,18 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
* Public. Can be invoked from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
[self enqueueJSCall:moduleDotMethod
withArguments:args
completionBlock:nil];
}
/**
* Public. Can be invoked from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod
withArguments:(NSArray *)arguments
completionBlock:(void (^)(void))completionBlock
{
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
@@ -1244,8 +1275,9 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID ?: @0, methodID ?: @0, args ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)];
arguments:@[moduleID ?: @0, methodID ?: @0, arguments ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)
completionBlock:completionBlock];
}
/**
@@ -1267,7 +1299,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
context:RCTGetExecutorID(_javaScriptExecutor)
completionBlock:nil];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
@@ -1333,7 +1366,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
* 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
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context
completionBlock:(void (^)(void))completionBlock
{
/**
* AnyThread
@@ -1349,10 +1386,13 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
}
id call = @{
@"module": module,
@"method": method,
@"args": args,
@"js_args": @{
@"module": module,
@"method": method,
@"args": args,
},
@"context": context ?: @0,
@"callback": (id)completionBlock ?: [NSNull null],
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
@@ -1365,7 +1405,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
}];
}
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context
completionBlock:(void (^)(void))completionBlock
{
RCTAssertJSThread();
@@ -1377,6 +1421,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self _handleBuffer:json context:context];
if (completionBlock) {
completionBlock();
}
};
[_javaScriptExecutor executeJSCall:module
@@ -1545,12 +1593,23 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
return [call[@"context"] isEqualToNumber:currentExecutorID];
}]];
if (calls.count > 0) {
void (^completionBlock)(void) = ^{
for (NSDictionary *call in calls) {
id callback = call[@"callback"];
if (callback && callback != [NSNull null]) {
((void (^)(void))callback)();
}
}
};
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"processBatch"
arguments:@[calls]
context:RCTGetExecutorID(_javaScriptExecutor)];
arguments:@[[calls valueForKey:@"js_args"]]
context:RCTGetExecutorID(_javaScriptExecutor)
completionBlock:completionBlock];
}
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);

View File

@@ -55,3 +55,12 @@ RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
// Return YES if image has an alpha component
RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
/**
* Helper for async tests, run the runloop while the condition becomes true or
* until timeout
*/
#define RCTRunLoopRunWhile(condition, timeout) \
_RCTRunLoopRunWhile(^BOOL{ return condition; }, timeout)
RCT_EXTERN BOOL _RCTRunLoopRunWhile(BOOL (^)(void), NSTimeInterval);

View File

@@ -273,3 +273,14 @@ BOOL RCTImageHasAlpha(CGImageRef image)
return YES;
}
}
BOOL _RCTRunLoopRunWhile(BOOL (^block)(void), NSTimeInterval timeout)
{
NSDate *timeoutDate = [[NSDate date] dateByAddingTimeInterval:timeout];
while (block() && [timeoutDate timeIntervalSinceNow] > 0) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
}
return !block();
}