Add the ability to pre-create the JavaScript thread with RCTJSCExecutor

Summary: This can be used to create a JavaScript thread and `JSContext` in advance, then supply them to the `RCTJSCExecutor` at creation time later.

Reviewed By: javache

Differential Revision: D3534553

fbshipit-source-id: 99ccf711928cd12e84c9fbe142c6d19a7af55d07
This commit is contained in:
Adam Ernst 2016-07-08 12:19:27 -07:00 коммит произвёл Facebook Github Bot 0
Родитель 294173a427
Коммит 39cb110c5b
2 изменённых файлов: 129 добавлений и 30 удалений

Просмотреть файл

@ -25,6 +25,17 @@ RCT_EXTERN NSString *const RCTJSCThreadName;
*/
RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
/**
* @experimental
* May be used to pre-create the JSContext to make RCTJSCExecutor creation less costly.
* Avoid using this; it's experimental and is not likely to be supported long-term.
*/
@interface RCTJSContextProvider : NSObject
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary;
@end
/**
* Uses a JavaScriptCore context as the execution engine.
*/
@ -43,6 +54,11 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
*/
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary;
/**
* Pass a RCTJSContextProvider object to use an NSThread/JSContext pair that have already been created.
*/
- (instancetype)initWithJSContextProvider:(RCTJSContextProvider *)JSContextProvider;
/**
* Create a NSError from a JSError object.
*

Просмотреть файл

@ -74,6 +74,18 @@ struct RandomAccessBundleStartupCode {
@implementation RCTCookieMap @end
#endif
struct RCTJSContextData {
BOOL useCustomJSCLibrary;
NSThread *javaScriptThread;
JSContext *context;
RCTJSCWrapper *jscWrapper;
};
@interface RCTJSContextProvider ()
/** May only be called once, or deadlock will result. */
- (RCTJSContextData)data;
@end
@interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@property (nonatomic, strong, readonly) JSContext *context;
@ -271,6 +283,21 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
}
}
static NSThread *newJavaScriptThread(void)
{
NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[RCTJSCExecutor class]
selector:@selector(runRunLoopThread)
object:nil];
javaScriptThread.name = RCTJSCThreadName;
if ([javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) {
[javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive];
} else {
javaScriptThread.threadPriority = [NSThread mainThread].threadPriority;
}
[javaScriptThread start];
return javaScriptThread;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
@ -287,24 +314,25 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
if (self = [super init]) {
_useCustomJSCLibrary = useCustomJSCLibrary;
_valid = YES;
_javaScriptThread = [[NSThread alloc] initWithTarget:[self class]
selector:@selector(runRunLoopThread)
object:nil];
_javaScriptThread.name = RCTJSCThreadName;
if ([_javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) {
[_javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive];
} else {
_javaScriptThread.threadPriority = [NSThread mainThread].threadPriority;
}
[_javaScriptThread start];
_javaScriptThread = newJavaScriptThread();
}
return self;
}
- (instancetype)initWithJSContextProvider:(RCTJSContextProvider *)JSContextProvider
{
if (self = [super init]) {
const RCTJSContextData data = JSContextProvider.data;
_useCustomJSCLibrary = data.useCustomJSCLibrary;
_valid = YES;
_javaScriptThread = data.javaScriptThread;
_jscWrapper = data.jscWrapper;
_context = [[RCTJavaScriptContext alloc] initWithJSContext:data.context onThread:_javaScriptThread];
}
return self;
}
- (RCTJavaScriptContext *)context
{
RCTAssertThread(_javaScriptThread, @"Must be called on JS thread.");
@ -341,26 +369,25 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
return;
}
[self->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary];
self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary);
[self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary];
JSContext *context = nil;
if (self->_jscWrapper) {
RCTAssert(self->_context != nil, @"If wrapper was pre-initialized, context should be too");
context = self->_context.context;
} else {
[self->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary];
self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary);
[self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary];
RCTAssert(self->_context == nil, @"Didn't expect to set up twice");
JSContext *context = [self->_jscWrapper->JSContext new];
self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
object:context];
RCTAssert(self->_context == nil, @"Didn't expect to set up twice");
context = [self->_jscWrapper->JSContext new];
self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
object:context];
if (self->_jscWrapper->configureJSContextForIOS != NULL) {
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
RCTAssert(cachesPath != nil, @"cachesPath should not be nil");
if (cachesPath) {
self->_jscWrapper->configureJSContextForIOS(context.JSGlobalContextRef, [cachesPath UTF8String]);
}
configureCacheOnContext(context, self->_jscWrapper);
installBasicSynchronousHooksOnContext(context);
}
[[self class] installSynchronousHooksOnContext:context];
__weak RCTJSCExecutor *weakSelf = self;
context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) {
@ -430,7 +457,20 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
}];
}
+ (void)installSynchronousHooksOnContext:(JSContext *)context
/** If configureJSContextForIOS is available on jscWrapper, calls it with the correct parameters. */
static void configureCacheOnContext(JSContext *context, RCTJSCWrapper *jscWrapper)
{
if (jscWrapper->configureJSContextForIOS != NULL) {
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
RCTAssert(cachesPath != nil, @"cachesPath should not be nil");
if (cachesPath) {
jscWrapper->configureJSContextForIOS(context.JSGlobalContextRef, [cachesPath UTF8String]);
}
}
}
/** Installs synchronous hooks that don't require a weak reference back to the RCTJSCExecutor. */
static void installBasicSynchronousHooksOnContext(JSContext *context)
{
context[@"noop"] = ^{};
context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) {
@ -889,3 +929,46 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name)
}
@end
@implementation RCTJSContextProvider
{
dispatch_semaphore_t _semaphore;
BOOL _useCustomJSCLibrary;
NSThread *_javaScriptThread;
JSContext *_context;
RCTJSCWrapper *_jscWrapper;
}
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary
{
if (self = [super init]) {
_semaphore = dispatch_semaphore_create(0);
_useCustomJSCLibrary = useCustomJSCLibrary;
_javaScriptThread = newJavaScriptThread();
[self performSelector:@selector(_createContext) onThread:_javaScriptThread withObject:nil waitUntilDone:NO];
}
return self;
}
- (void)_createContext
{
_jscWrapper = RCTJSCWrapperCreate(_useCustomJSCLibrary);
_context = [_jscWrapper->JSContext new];
configureCacheOnContext(_context, _jscWrapper);
installBasicSynchronousHooksOnContext(_context);
dispatch_semaphore_signal(_semaphore);
}
- (RCTJSContextData)data
{
// Be sure this method is only called once, otherwise it will hang here forever:
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
return {
.useCustomJSCLibrary = _useCustomJSCLibrary,
.javaScriptThread = _javaScriptThread,
.context = _context,
.jscWrapper = _jscWrapper,
};
}
@end