243 lines
8.5 KiB
Objective-C
243 lines
8.5 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "AppDelegate.h"
|
|
|
|
#import "RCTRootView.h"
|
|
#import "RCTEventDispatcher.h"
|
|
#import "ABYServer.h"
|
|
#import "ABYContextManager.h"
|
|
#import "RCTContextExecutor.h"
|
|
|
|
/**
|
|
This class exists so that a client-created `JSGlobalContextRef`
|
|
instance and optional JavaScript thread can be injected
|
|
into an `RCTContextExecutor`.
|
|
*/
|
|
@interface ABYContextExecutor : RCTContextExecutor
|
|
|
|
/**
|
|
Sets the JavaScript thread that will be used when `init`ing
|
|
an instance of this class. If not set, `[NSThread mainThread]`
|
|
will be used.
|
|
|
|
@param thread the thread
|
|
*/
|
|
+(void) setJavaScriptThread:(NSThread*)thread;
|
|
|
|
/**
|
|
Sets the context that will be used when `init`ing an instance
|
|
of this class.
|
|
@param context the context
|
|
*/
|
|
+(void) setContext:(JSGlobalContextRef)context;
|
|
|
|
@end
|
|
|
|
static NSThread* staticJavaScriptThread = nil;
|
|
static JSGlobalContextRef staticContext;
|
|
|
|
@implementation ABYContextExecutor
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (instancetype)init
|
|
{
|
|
id me = [self initWithJavaScriptThread:(staticJavaScriptThread ? staticJavaScriptThread : [NSThread mainThread])
|
|
globalContextRef:staticContext];
|
|
staticJavaScriptThread = nil;
|
|
JSGlobalContextRelease(staticContext);
|
|
return me;
|
|
}
|
|
|
|
+(void) setJavaScriptThread:(NSThread*)thread
|
|
{
|
|
staticJavaScriptThread = thread;
|
|
}
|
|
|
|
+(void) setContext:(JSGlobalContextRef)context
|
|
{
|
|
staticContext = JSGlobalContextRetain(context);
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface AppDelegate()
|
|
|
|
@property (strong, nonatomic) ABYServer* replServer;
|
|
@property (strong, nonatomic) ABYContextManager* contextManager;
|
|
@property (strong, nonatomic) NSURL* compilerOutputDirectory;
|
|
|
|
@end
|
|
|
|
@implementation AppDelegate
|
|
|
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
{
|
|
NSURL *jsCodeLocation;
|
|
|
|
/**
|
|
* Loading JavaScript code - uncomment the one you want.
|
|
*
|
|
* OPTION 1
|
|
* Load from development server. Start the server from the repository root:
|
|
*
|
|
* $ npm start
|
|
*
|
|
* To run on device, change `localhost` to the IP address of your computer
|
|
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
|
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
|
* on the same Wi-Fi network.
|
|
*/
|
|
|
|
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
|
|
|
|
/**
|
|
* OPTION 2
|
|
* Load from pre-bundled file on disk. To re-generate the static bundle
|
|
* from the root of your project directory, run
|
|
*
|
|
* $ react-native bundle --minify
|
|
*
|
|
* see http://facebook.github.io/react-native/docs/runningondevice.html
|
|
*/
|
|
|
|
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
|
|
// Set up the ClojureScript compiler output directory
|
|
self.compilerOutputDirectory = [[self privateDocumentsDirectory] URLByAppendingPathComponent:@"cljs-out"];
|
|
|
|
// Set up our context manager
|
|
self.contextManager = [[ABYContextManager alloc] initWithContext:JSGlobalContextCreate(NULL)
|
|
compilerOutputDirectory:self.compilerOutputDirectory];
|
|
|
|
// Inject our context using ABYContextExecutor
|
|
[ABYContextExecutor setContext:self.contextManager.context];
|
|
|
|
// Set React Native to intstantiate our ABYContextExecutor, doing this by slipping the executorClass
|
|
// assignement between alloc and initWithBundleURL:moduleProvider:launchOptions:
|
|
RCTBridge *bridge = [RCTBridge alloc];
|
|
bridge.executorClass = [ABYContextExecutor class];
|
|
bridge = [bridge initWithBundleURL:jsCodeLocation
|
|
moduleProvider:nil
|
|
launchOptions:launchOptions];
|
|
|
|
// Set up a root view using the bridge defined above
|
|
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
|
|
moduleName:@"$PROJECT_NAME$"
|
|
initialProperties:nil];
|
|
|
|
// Set up to be notified when the React Native UI is up
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(contentDidAppear)
|
|
name:RCTContentDidAppearNotification
|
|
object:rootView];
|
|
|
|
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
UIViewController *rootViewController = [[UIViewController alloc] init];
|
|
rootViewController.view = rootView;
|
|
self.window.rootViewController = rootViewController;
|
|
[self.window makeKeyAndVisible];
|
|
return YES;
|
|
}
|
|
|
|
- (NSURL *)privateDocumentsDirectory
|
|
{
|
|
NSURL *libraryDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
|
|
|
|
return [libraryDirectory URLByAppendingPathComponent:@"Private Documents"];
|
|
}
|
|
|
|
- (void)createDirectoriesUpTo:(NSURL*)directory
|
|
{
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:[directory path]]) {
|
|
NSError *error = nil;
|
|
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:[directory path]
|
|
withIntermediateDirectories:YES
|
|
attributes:nil
|
|
error:&error]) {
|
|
NSLog(@"Can't create directory %@ [%@]", [directory path], error);
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)requireAppNamespaces:(JSContext*)context
|
|
{
|
|
[context evaluateScript:[NSString stringWithFormat:@"goog.require('%@');", [self munge:@"$PROJECT_NAME_HYPHENATED$.ios.core"]]];
|
|
}
|
|
|
|
- (JSValue*)getValue:(NSString*)name inNamespace:(NSString*)namespace fromContext:(JSContext*)context
|
|
{
|
|
JSValue* namespaceValue = nil;
|
|
for (NSString* namespaceElement in [namespace componentsSeparatedByString: @"."]) {
|
|
if (namespaceValue) {
|
|
namespaceValue = namespaceValue[[self munge:namespaceElement]];
|
|
} else {
|
|
namespaceValue = context[[self munge:namespaceElement]];
|
|
}
|
|
}
|
|
|
|
return namespaceValue[[self munge:name]];
|
|
}
|
|
|
|
- (NSString*)munge:(NSString*)s
|
|
{
|
|
return [[[s stringByReplacingOccurrencesOfString:@"-" withString:@"_"]
|
|
stringByReplacingOccurrencesOfString:@"!" withString:@"_BANG_"]
|
|
stringByReplacingOccurrencesOfString:@"?" withString:@"_QMARK_"];
|
|
}
|
|
|
|
- (void)contentDidAppear
|
|
{
|
|
// Ensure private documents directory exists
|
|
[self createDirectoriesUpTo:[self privateDocumentsDirectory]];
|
|
|
|
// Copy resources from bundle "out" to compilerOutputDirectory
|
|
|
|
NSFileManager* fileManager = [NSFileManager defaultManager];
|
|
fileManager.delegate = self;
|
|
|
|
// First blow away old compiler output directory
|
|
[fileManager removeItemAtPath:self.compilerOutputDirectory.path error:nil];
|
|
|
|
// Copy files from bundle to compiler output driectory
|
|
NSString *outPath = [[NSBundle mainBundle] pathForResource:@"out" ofType:nil];
|
|
[fileManager copyItemAtPath:outPath toPath:self.compilerOutputDirectory.path error:nil];
|
|
|
|
[self.contextManager setUpAmblyImportScript];
|
|
|
|
NSString* mainJsFilePath = [[self.compilerOutputDirectory URLByAppendingPathComponent:@"main" isDirectory:NO] URLByAppendingPathExtension:@"js"].path;
|
|
|
|
NSURL* googDirectory = [self.compilerOutputDirectory URLByAppendingPathComponent:@"goog"];
|
|
|
|
[self.contextManager bootstrapWithDepsFilePath:mainJsFilePath
|
|
googBasePath:[[googDirectory URLByAppendingPathComponent:@"base" isDirectory:NO] URLByAppendingPathExtension:@"js"].path];
|
|
|
|
JSContext* context = [JSContext contextWithJSGlobalContextRef:self.contextManager.context];
|
|
[self requireAppNamespaces:context];
|
|
|
|
JSValue* initFn = [self getValue:@"init" inNamespace:@"$PROJECT_NAME_HYPHENATED$.ios.core" fromContext:context];
|
|
NSAssert(!initFn.isUndefined, @"Could not find the app init function");
|
|
[initFn callWithArguments:@[]];
|
|
|
|
// Send a nonsense UI event to cause React Native to load our Reagent UI
|
|
RCTRootView* rootView = (RCTRootView*)self.window.rootViewController.view;
|
|
[rootView.bridge.modules[@"RCTEventDispatcher"] sendInputEventWithName:@"dummy" body:@{@"target": @1}];
|
|
|
|
// Now that React Native has been initialized, fire up our REPL server
|
|
self.replServer = [[ABYServer alloc] initWithContext:self.contextManager.context
|
|
compilerOutputDirectory:self.compilerOutputDirectory];
|
|
[self.replServer startListening];
|
|
}
|
|
|
|
@end
|