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; 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. * Uses a JavaScriptCore context as the execution engine.
*/ */
@ -43,6 +54,11 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
*/ */
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary; - (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. * Create a NSError from a JSError object.
* *

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

@ -74,6 +74,18 @@ struct RandomAccessBundleStartupCode {
@implementation RCTCookieMap @end @implementation RCTCookieMap @end
#endif #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> @interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@property (nonatomic, strong, readonly) JSContext *context; @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 - (void)setBridge:(RCTBridge *)bridge
{ {
_bridge = bridge; _bridge = bridge;
@ -287,21 +314,22 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
if (self = [super init]) { if (self = [super init]) {
_useCustomJSCLibrary = useCustomJSCLibrary; _useCustomJSCLibrary = useCustomJSCLibrary;
_valid = YES; _valid = YES;
_javaScriptThread = newJavaScriptThread();
_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]; 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; return self;
} }
@ -341,25 +369,24 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
return; return;
} }
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->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary];
self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary); self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary);
[self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary]; [self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary];
RCTAssert(self->_context == nil, @"Didn't expect to set up twice"); RCTAssert(self->_context == nil, @"Didn't expect to set up twice");
JSContext *context = [self->_jscWrapper->JSContext new]; context = [self->_jscWrapper->JSContext new];
self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread]; self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
object:context]; object:context];
if (self->_jscWrapper->configureJSContextForIOS != NULL) { configureCacheOnContext(context, self->_jscWrapper);
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; installBasicSynchronousHooksOnContext(context);
RCTAssert(cachesPath != nil, @"cachesPath should not be nil");
if (cachesPath) {
self->_jscWrapper->configureJSContextForIOS(context.JSGlobalContextRef, [cachesPath UTF8String]);
} }
}
[[self class] installSynchronousHooksOnContext:context];
__weak RCTJSCExecutor *weakSelf = self; __weak RCTJSCExecutor *weakSelf = self;
@ -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[@"noop"] = ^{};
context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) { context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) {
@ -889,3 +929,46 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name)
} }
@end @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