Skip to content

Commit

Permalink
[ReactNative] Batch JS -> Native calls per queue
Browse files Browse the repository at this point in the history
Summary:
@public

For every call that comes from JS to Native we'd call dispatch_async so the
method would be cllead on the queue/thread it's expecting. This diff
buckets the calls by target queue, dispatches only once to that queue, and then
call all the methods for it inside the same dispatch.

Test Plan: {F22510090}
  • Loading branch information
tadeuzagallo committed Jun 9, 2015
1 parent 459882e commit 2a08897
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 38 deletions.
89 changes: 51 additions & 38 deletions React/Base/RCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -1379,15 +1379,42 @@ - (void)_handleBuffer:(id)buffer context:(NSNumber *)context
return;
}

// TODO: if we sort the requests by module, we could dispatch once per
// module instead of per request, which would reduce the call overhead.
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:_queuesByID.count];
for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool {
[self _handleRequestNumber:i
moduleID:[moduleIDs[i] integerValue]
methodID:[methodIDs[i] integerValue]
params:paramsArrays[i]
context:context];
id queue = RCTNullIfNil(_queuesByID[moduleIDs[i]]);
NSMutableOrderedSet *set = [buckets objectForKey:queue];
if (!set) {
set = [[NSMutableOrderedSet alloc] init];
[buckets setObject:set forKey:queue];
}
[set addObject:@(i)];
}

for (id queue in buckets) {
RCTProfileBeginFlowEvent();
dispatch_block_t block = ^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();

NSOrderedSet *calls = [buckets objectForKey:queue];
@autoreleasepool {
for (NSNumber *indexObj in calls) {
NSUInteger index = indexObj.unsignedIntegerValue;
[self _handleRequestNumber:index
moduleID:[moduleIDs[index] integerValue]
methodID:[methodIDs[index] integerValue]
params:paramsArrays[index]
context:context];
}
}

RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
};

if (queue == (id)kCFNull) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else {
dispatch_async(queue, block);
}
}

Expand All @@ -1407,8 +1434,6 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
params:(NSArray *)params
context:(NSNumber *)context
{
RCTAssertJSThread();

if (!self.isValid) {
return NO;
}
Expand All @@ -1426,6 +1451,8 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
return NO;
}

RCTProfileBeginEvent();

RCTModuleMethod *method = methods[methodID];

// Look up module
Expand All @@ -1435,36 +1462,22 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
return NO;
}

RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[self dispatchBlock:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
RCTBatchedBridge *strongSelf = weakSelf;

if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}

@try {
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
@throw exception;
}
@try {
[method invokeWithBridge:self module:module arguments:params context:context];
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
@throw exception;
}
}

RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
@"module": method.moduleClassName,
@"method": method.JSMethodName,
@"selector": NSStringFromSelector(method.selector),
@"args": RCTJSONStringify(params ?: [NSNull null], NULL),
});
} forModuleID:@(moduleID)];
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
@"module": method.moduleClassName,
@"method": method.JSMethodName,
@"selector": NSStringFromSelector(method.selector),
@"args": RCTJSONStringify(params ?: [NSNull null], NULL),
});

return YES;
}
Expand Down
3 changes: 3 additions & 0 deletions React/Base/RCTUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);

// Return YES if image has an alpha component
RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);

RCT_EXTERN id RCTNullIfNil(id value);
RCT_EXTERN id RCTNilIfNull(id value);
10 changes: 10 additions & 0 deletions React/Base/RCTUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,13 @@ BOOL RCTImageHasAlpha(CGImageRef image)
return YES;
}
}

id RCTNullIfNil(id value)
{
return value ?: (id)kCFNull;
}

id RCTNilIfNull(id value)
{
return value == (id)kCFNull ? nil : value;
}

0 comments on commit 2a08897

Please sign in to comment.