[ReactNative] Unfork RKRootView

This commit is contained in:
Tadeu Zagallo 2015-04-02 07:33:21 -07:00
parent b1e502a083
commit f370f9cbc4
15 changed files with 448 additions and 298 deletions

View File

@ -21,6 +21,8 @@
// This is used to give meaningful names to snapshot image files. // This is used to give meaningful names to snapshot image files.
@property (nonatomic, assign) SEL testSelector; @property (nonatomic, assign) SEL testSelector;
@property (nonatomic, weak) UIView *view;
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view; - (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;
@end @end

View File

@ -17,15 +17,6 @@
#define TIMEOUT_SECONDS 240 #define TIMEOUT_SECONDS 240
@interface RCTRootView (Testing)
- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider;
@end
@implementation RCTTestRunner @implementation RCTTestRunner
{ {
FBSnapshotTestController *_snapshotController; FBSnapshotTestController *_snapshotController;
@ -75,17 +66,17 @@
RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil]; RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil];
testModule.testSelector = test; testModule.testSelector = test;
RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:_script
RCTRootView *rootView = [[RCTRootView alloc] _initWithBundleURL:[NSURL URLWithString:_script] moduleProvider:^(){
moduleName:moduleName
launchOptions:nil
moduleProvider:^{
return @[testModule]; return @[testModule];
}]; }
[testModule setValue:rootView forKey:@"_view"]; launchOptions:nil];
rootView.frame = CGRectMake(0, 0, 320, 2000); RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName];
testModule.view = rootView;
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
rootView.initialProperties = initialProps; rootView.initialProperties = initialProps;
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
@ -94,8 +85,9 @@
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
error = [[RCTRedBox sharedInstance] currentErrorMessage]; error = [[RCTRedBox sharedInstance] currentErrorMessage];
} }
[rootView invalidate];
[rootView removeFromSuperview]; [rootView removeFromSuperview];
[rootView.bridge invalidate];
[rootView invalidate];
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view); RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
vc.view = nil; vc.view = nil;
[[RCTRedBox sharedInstance] dismiss]; [[RCTRedBox sharedInstance] dismiss];

View File

@ -168,6 +168,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
- (void)invalidate - (void)invalidate
{ {
[_jsQueue cancelAllOperations];
_socket.delegate = nil; _socket.delegate = nil;
[_socket closeWithCode:1000 reason:@"Invalidated"]; [_socket closeWithCode:1000 reason:@"Invalidated"];
_socket = nil; _socket = nil;

View File

@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import <UIKit/UIKit.h>
#import "RCTBridgeModule.h" #import "RCTBridgeModule.h"
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h" #import "RCTJavaScriptExecutor.h"
@ -24,11 +26,15 @@
*/ */
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void); typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
extern NSString *const RCTReloadBridge;
/** /**
* Async batched bridge used to communicate with the JavaScript application. * Async batched bridge used to communicate with the JavaScript application.
*/ */
@interface RCTBridge : NSObject <RCTInvalidating> @interface RCTBridge : NSObject <RCTInvalidating>
@property (nonatomic, assign, readonly, getter=isLoaded) BOOL loaded;
/** /**
* The designated initializer. This creates a new bridge on top of the specified * The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication * executor. The bridge should then be used for all subsequent communication
@ -55,6 +61,8 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
*/ */
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete; - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
@property (nonatomic, strong) Class executorClass;
/** /**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a * The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
* higher-level interface for sending UI events such as touches and text input. * higher-level interface for sending UI events such as touches and text input.
@ -89,4 +97,6 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
*/ */
+ (BOOL)hasValidJSExecutor; + (BOOL)hasValidJSExecutor;
- (void)reload;
@end @end

View File

@ -16,11 +16,16 @@
#import <mach-o/dyld.h> #import <mach-o/dyld.h>
#import <mach-o/getsect.h> #import <mach-o/getsect.h>
#import "RCTContextExecutor.h"
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTRootView.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#import "RCTWebViewExecutor.h"
/** /**
* Must be kept in sync with `MessageQueue.js`. * Must be kept in sync with `MessageQueue.js`.
@ -34,6 +39,8 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis RCTBridgeFieldFlushDateMillis
}; };
NSString *const RCTReloadBridge = @"RCTReloadBridge";
/** /**
* This function returns the module name for a given class. * This function returns the module name for a given class.
*/ */
@ -125,6 +132,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
NSString *_methodName; NSString *_methodName;
} }
static Class _globalExecutorClass;
- (instancetype)initWithMethodName:(NSString *)methodName - (instancetype)initWithMethodName:(NSString *)methodName
JSMethodName:(NSString *)JSMethodName JSMethodName:(NSString *)JSMethodName
{ {
@ -497,33 +506,41 @@ static NSDictionary *RCTLocalModulesConfig()
RCTSparseArray *_modulesByID; RCTSparseArray *_modulesByID;
NSDictionary *_modulesByName; NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor; id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSString *_bundlePath;
NSDictionary *_launchOptions;
RCTBridgeModuleProviderBlock _moduleProvider; RCTBridgeModuleProviderBlock _moduleProvider;
BOOL _loaded;
} }
static id<RCTJavaScriptExecutor> _latestJSExecutor; static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithBundlePath:(NSString *)bundlepath - (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions launchOptions:(NSDictionary *)launchOptions
{ {
if ((self = [super init])) { if ((self = [super init])) {
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _bundlePath = bundlePath;
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
_moduleProvider = block; _moduleProvider = block;
_launchOptions = launchOptions; _launchOptions = launchOptions;
[self setUp];
[self bindKeys];
} }
return self; return self;
} }
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor
{
_javaScriptExecutor = executor;
_latestJSExecutor = _javaScriptExecutor;
[self setUp];
}
- (void)setUp - (void)setUp
{ {
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
if ([NSStringFromClass(executorClass) isEqualToString:@"RCTWebViewExecutor"]) {
_javaScriptExecutor = [[RCTWebViewExecutor alloc] initWithWebView:[[UIWebView alloc] init]];
} else {
_javaScriptExecutor = [[executorClass alloc] init];
}
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Register passed-in module instances // Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) { for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
@ -574,11 +591,58 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
dispatch_semaphore_signal(semaphore); dispatch_semaphore_signal(semaphore);
}]; }];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogError(@"JavaScriptExecutor took too long to inject JSON object"); if (_bundlePath != nil) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:[NSURL URLWithString:_bundlePath]
onComplete:^(NSError *error) {
_loaded = YES;
if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
}
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
;
}
}];
} }
} }
- (void)bindKeys
{
#if TARGET_IPHONE_SIMULATOR
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[self reload];
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_executorClass = Nil;
[self reload];
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!_executorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
[self reload];
}];
#endif
}
- (NSDictionary *)modules - (NSDictionary *)modules
{ {
@ -602,6 +666,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)invalidate - (void)invalidate
{ {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});
// Release executor // Release executor
if (_latestJSExecutor == _javaScriptExecutor) { if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil; _latestJSExecutor = nil;
@ -609,11 +680,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[_javaScriptExecutor invalidate]; [_javaScriptExecutor invalidate];
_javaScriptExecutor = nil; _javaScriptExecutor = nil;
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});
// Invalidate modules // Invalidate modules
for (id target in _modulesByID.allObjects) { for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) { if ([target respondsToSelector:@selector(invalidate)]) {
@ -624,6 +690,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Release modules (breaks retain cycle if module has strong bridge reference) // Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil; _modulesByID = nil;
_modulesByName = nil; _modulesByName = nil;
_loaded = NO;
} }
/** /**
@ -647,10 +714,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod]; NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod); RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (self.loaded) {
[self _invokeAndProcessModule:@"BatchedBridge" [self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue" method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]]; arguments:@[moduleID, methodID, args ?: @[]]];
} }
}
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{ {
@ -793,6 +862,19 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return YES; return YES;
} }
- (void)reload
{
if (_loaded) {
// If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render.
[self invalidate];
[self setUp];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadViewsNotification
object:self];
}
}
+ (BOOL)hasValidJSExecutor + (BOOL)hasValidJSExecutor
{ {
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]); return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);

View File

@ -9,11 +9,11 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@class RCTRootView; @class RCTBridge;
@interface RCTDevMenu : NSObject @interface RCTDevMenu : NSObject
- (instancetype)initWithRootView:(RCTRootView *)rootView; - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)show; - (void)show;
@end @end

View File

@ -11,28 +11,29 @@
#import "RCTRedBox.h" #import "RCTRedBox.h"
#import "RCTRootView.h" #import "RCTRootView.h"
#import "RCTSourceCode.h"
@interface RCTDevMenu () <UIActionSheetDelegate> { @interface RCTDevMenu () <UIActionSheetDelegate> {
BOOL _liveReload; BOOL _liveReload;
} }
@property (nonatomic, weak) RCTRootView *view; @property (nonatomic, weak) RCTBridge *bridge;
@end @end
@implementation RCTDevMenu @implementation RCTDevMenu
- (instancetype)initWithRootView:(RCTRootView *)rootView - (instancetype)initWithBridge:(RCTBridge *)bridge
{ {
if (self = [super init]) { if (self = [super init]) {
self.view = rootView; _bridge = bridge;
} }
return self; return self;
} }
- (void)show - (void)show
{ {
NSString *debugTitle = self.view.executorClass == nil ? @"Enable Debugging" : @"Disable Debugging"; NSString *debugTitle = self.bridge.executorClass == Nil ? @"Enable Debugging" : @"Disable Debugging";
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload"; NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self delegate:self
@ -40,16 +41,16 @@
destructiveButtonTitle:nil destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil]; otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack; actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:self.view]; [actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
} }
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{ {
if (buttonIndex == 0) { if (buttonIndex == 0) {
[self.view reload]; [self.bridge reload];
} else if (buttonIndex == 1) { } else if (buttonIndex == 1) {
self.view.executorClass = self.view.executorClass == nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil; self.bridge.executorClass = self.bridge.executorClass == Nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil;
[self.view reload]; [self.bridge reload];
} else if (buttonIndex == 2) { } else if (buttonIndex == 2) {
_liveReload = !_liveReload; _liveReload = !_liveReload;
[self _pollAndReload]; [self _pollAndReload];
@ -59,7 +60,8 @@
- (void)_pollAndReload - (void)_pollAndReload
{ {
if (_liveReload) { if (_liveReload) {
NSURL *url = [self.view scriptURL]; RCTSourceCode *sourceCodeModule = self.bridge.modules[NSStringFromClass([RCTSourceCode class])];
NSURL *url = sourceCodeModule.scriptURL;
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url]; NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL]; [self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
} }
@ -75,7 +77,7 @@
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (_liveReload && response.statusCode == 205) { if (_liveReload && response.statusCode == 205) {
[[RCTRedBox sharedInstance] dismiss]; [[RCTRedBox sharedInstance] dismiss];
[self.view reload]; [self.bridge reload];
} }
[self _pollAndReload]; [self _pollAndReload];
}); });

View File

@ -0,0 +1,22 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import "RCTJavaScriptExecutor.h"
@class RCTBridge;
/**
* Class that allows easy embedding, loading, life-cycle management of a
* JavaScript application inside of a native application.
* TODO: Before loading new application source, publish global notification in
* JavaScript so that applications can clean up resources. (launch blocker).
* TODO: Incremental module loading. (low pri).
*/
@interface RCTJavaScriptLoader : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(RCTJavaScriptCompleteBlock)onComplete;
@end

135
React/Base/RCTJavaScriptLoader.m Executable file
View File

@ -0,0 +1,135 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTJavaScriptLoader.h"
#import "RCTBridge.h"
#import "RCTInvalidating.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
#define CACHE_DIR @"RCTJSBundleCache"
#pragma mark - Application Engine
/**
* TODO:
* - Add window resize rotation events matching the DOM API.
* - Device pixel ration hooks.
* - Source maps.
*/
@implementation RCTJavaScriptLoader
{
RCTBridge *_bridge;
}
/**
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
* engine in its own dedicated thread.
*
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
* additional GCD dispatch per frame and likely makes it so that other UIThread
* operations don't delay the dispatch (so we can begin working in JS much
* faster.) Event handling must still be sent via a GCD dispatch, of course.
*
* We must add the display link to two runloops in order to get setTimeouts to
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
* TODO: We can invent a `requestAnimationFrame` and
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
* an animation.
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
*
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
{
if (!scriptURL) {
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader"
code:1
userInfo:@{NSLocalizedDescriptionKey: @"No script URL provided"}];
onComplete(error);
return;
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root",
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
onComplete(error);
return;
}
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": @[@{
@"methodName": errorDetails[@"description"] ?: @"",
@"file": errorDetails[@"filename"] ?: @"",
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
}]
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
onComplete(error);
return;
}
// Success!
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *_error) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(error);
});
}];
}];
[task resume];
}
@end

View File

@ -11,18 +11,25 @@
#import "RCTBridge.h" #import "RCTBridge.h"
extern NSString *const RCTJavaScriptDidLoadNotification;
extern NSString *const RCTReloadNotification;
extern NSString *const RCTReloadViewsNotification;
@interface RCTRootView : UIView <RCTInvalidating> @interface RCTRootView : UIView <RCTInvalidating>
- (instancetype)initWithBundleURL:(NSURL *)bundleURL - (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName moduleName:(NSString *)moduleName NS_DESIGNATED_INITIALIZER;
launchOptions:(NSDictionary *)launchOptions /* NS_DESIGNATED_INITIALIZER */;
/** /**
* The URL of the bundled application script (required). * - Convenience initializer -
* Setting this will clear the view contents, and trigger * A bridge will be created internally.
* an asynchronous load/download and execution of the script. * This initializer is intended to be used when the app has a single RCTRootView,
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
* to all the instances.
*/ */
@property (nonatomic, strong, readonly) NSURL *scriptURL; - (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions;
/** /**
* The name of the JavaScript module to execute within the * The name of the JavaScript module to execute within the
@ -32,12 +39,7 @@
*/ */
@property (nonatomic, copy, readonly) NSString *moduleName; @property (nonatomic, copy, readonly) NSString *moduleName;
/** @property (nonatomic, strong, readonly) RCTBridge *bridge;
* A block that returns an array of pre-allocated modules. These
* modules will take precedence over any automatically registered
* modules of the same name.
*/
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
/** /**
* The default properties to apply to the view when the script bundle * The default properties to apply to the view when the script bundle
@ -64,6 +66,10 @@
- (void)reload; - (void)reload;
+ (void)reloadAll; + (void)reloadAll;
@property (nonatomic, weak) UIViewController *backingViewController;
@property (nonatomic, strong, readonly) UIView *contentView;
- (void)startOrResetInteractionTiming; - (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming; - (NSDictionary *)endAndResetInteractionTiming;

View File

@ -9,6 +9,8 @@
#import "RCTRootView.h" #import "RCTRootView.h"
#import <objc/runtime.h>
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTContextExecutor.h" #import "RCTContextExecutor.h"
#import "RCTDevMenu.h" #import "RCTDevMenu.h"
@ -23,7 +25,9 @@
#import "RCTWebViewExecutor.h" #import "RCTWebViewExecutor.h"
#import "UIView+React.h" #import "UIView+React.h"
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTReloadNotification = @"RCTReloadNotification"; NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
/** /**
* HACK(t6568049) This should be removed soon, hiding to prevent people from * HACK(t6568049) This should be removed soon, hiding to prevent people from
@ -35,95 +39,113 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification";
@end @end
@interface RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag;
@end
@implementation RCTRootView @implementation RCTRootView
{ {
RCTDevMenu *_devMenu; RCTDevMenu *_devMenu;
RCTBridge *_bridge; RCTBridge *_bridge;
RCTTouchHandler *_touchHandler; RCTTouchHandler *_touchHandler;
id<RCTJavaScriptExecutor> _executor; NSString *_moduleName;
BOOL _registered; BOOL _registered;
NSDictionary *_launchOptions; NSDictionary *_launchOptions;
UIView *_contentView;
} }
static Class _globalExecutorClass; - (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
+ (void)initialize
{ {
RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
#if TARGET_IPHONE_SIMULATOR if ((self = [super init])) {
#ifdef DEBUG
// Register Cmd-R as a global refresh key _enableDevMenu = YES;
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[self reloadAll];
}];
// Cmd-D reloads using the web view executor, allows attaching from Safari dev tools.
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_globalExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!_globalExecutorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
[self reloadAll];
}];
#endif #endif
_bridge = bridge;
_moduleName = moduleName;
self.backgroundColor = [UIColor whiteColor];
[self setUp];
}
return self;
} }
- (instancetype)initWithBundleURL:(NSURL *)bundleURL - (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions launchOptions:(NSDictionary *)launchOptions
{ {
if ((self = [super init])) { RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:bundleURL.absoluteString
RCTAssert(bundleURL, @"A bundleURL is required to create an RCTRootView"); moduleProvider:nil
RCTAssert(moduleName, @"A bundleURL is required to create an RCTRootView"); launchOptions:launchOptions];
_moduleName = moduleName; return [self initWithBridge:bridge
_launchOptions = launchOptions; moduleName:moduleName];
[self setUp];
[self setScriptURL:bundleURL];
}
return self;
} }
/** - (void)dealloc
* HACK(t6568049) Private constructor for testing purposes
*/
- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider
{ {
if ((self = [super init])) { [self tearDown];
_moduleProvider = moduleProvider;
_moduleName = moduleName;
_launchOptions = launchOptions;
[self setUp];
[self setScriptURL:bundleURL];
}
return self;
} }
- (void)setUp - (void)setUp
{ {
// Every root view that is created must have a unique react tag. if (!_registered) {
// Numbering of these tags goes from 1, 11, 21, 31, etc /**
static NSInteger rootViewTag = 1; * Every root view that is created must have a unique react tag.
self.reactTag = @(rootViewTag); * Numbering of these tags goes from 1, 11, 21, 31, etc
#ifdef DEBUG *
self.enableDevMenu = YES; * NOTE: Since the bridge persists, the RootViews might be reused, so now
#endif * the react tag is assigned every time we load new content.
self.backgroundColor = [UIColor whiteColor]; */
rootViewTag += 10; _contentView = [[UIView alloc] init];
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_contentView addGestureRecognizer:_touchHandler];
[self addSubview:_contentView];
// Add reload observer
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload) selector:@selector(reload)
name:RCTReloadNotification name:RCTReloadViewsNotification
object:nil]; object:_bridge];
if (_bridge.loaded) {
[self bundleFinishedLoading];
} else {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
}
}
}
- (void)tearDown
{
if (_registered) {
_registered = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_contentView removeGestureRecognizer:_touchHandler];
[_contentView removeFromSuperview];
[_touchHandler invalidate];
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[_contentView.reactTag]];
}
}
- (BOOL)isValid
{
return _registered;
}
- (void)invalidate
{
[self tearDown];
}
- (UIViewController *)backingViewController {
return _backingViewController ?: [super backingViewController];
} }
- (BOOL)canBecomeFirstResponder - (BOOL)canBecomeFirstResponder
@ -135,7 +157,7 @@ static Class _globalExecutorClass;
{ {
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) { if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
if (!_devMenu) { if (!_devMenu) {
_devMenu = [[RCTDevMenu alloc] initWithRootView:self]; _devMenu = [[RCTDevMenu alloc] initWithBridge:self.bridge];
} }
[_devMenu show]; [_devMenu show];
} }
@ -149,194 +171,51 @@ static Class _globalExecutorClass;
]; ];
} }
- (void)dealloc - (void)bundleFinishedLoading
{ {
[[NSNotificationCenter defaultCenter] removeObserver:self]; dispatch_async(dispatch_get_main_queue(), ^{
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
[self invalidate];
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return [_bridge isValid];
}
- (void)invalidate
{
// Clear view
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self removeGestureRecognizer:_touchHandler];
[_touchHandler invalidate];
[_executor invalidate];
// TODO: eventually we'll want to be able to share the bridge between
// multiple rootviews, in which case we'll need to move this elsewhere
[_bridge invalidate];
}
#pragma mark Bundle loading
- (void)bundleFinishedLoading:(NSError *)error
{
if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
}
} else {
[_bridge.uiManager registerRootView:self];
_registered = YES; _registered = YES;
NSString *moduleName = _moduleName ?: @""; NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{ NSDictionary *appParameters = @{
@"rootTag": self.reactTag, @"rootTag": _contentView.reactTag,
@"initialProps": self.initialProperties ?: @{}, @"initialProps": self.initialProperties ?: @{},
}; };
[_bridge.uiManager registerRootView:_contentView];
[_bridge enqueueJSCall:@"AppRegistry.runApplication" [_bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]]; args:@[moduleName, appParameters]];
}
}
- (void)loadBundle
{
[self invalidate];
if (!_scriptURL) {
return;
}
// Clean up
[self removeGestureRecognizer:_touchHandler];
[_touchHandler invalidate];
[_executor invalidate];
[_bridge invalidate];
_registered = NO;
// Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
/**
* HACK(t6568049) Most of the properties passed into the bridge are not used
* right now but it'll be changed soon so it's here for convenience.
*/
_bridge = [[RCTBridge alloc] initWithBundlePath:_scriptURL.absoluteString
moduleProvider:_moduleProvider
launchOptions:_launchOptions];
[_bridge setJavaScriptExecutor:_executor];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self addGestureRecognizer:_touchHandler];
// Load the bundle
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:_scriptURL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from React root",
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
[self bundleFinishedLoading:error];
return;
}
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": @[@{
@"methodName": errorDetails[@"description"] ?: @"",
@"file": errorDetails[@"filename"] ?: @"",
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
}]
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
[self bundleFinishedLoading:error];
return;
}
if (!_bridge.isValid) {
return; // Bridge was invalidated in the meanwhile
}
// Success!
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = _scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *_error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (_bridge.isValid) {
[self bundleFinishedLoading:_error];
}
}); });
}];
}];
[task resume];
}
- (void)setScriptURL:(NSURL *)scriptURL
{
if ([_scriptURL isEqual:scriptURL]) {
return;
}
_scriptURL = scriptURL;
[self loadBundle];
} }
- (void)layoutSubviews - (void)layoutSubviews
{ {
[super layoutSubviews]; [super layoutSubviews];
_contentView.frame = self.bounds;
if (_registered) { if (_registered) {
[_bridge.uiManager setFrame:self.frame forRootView:self]; [_bridge.uiManager setFrame:self.frame forRootView:_contentView];
} }
} }
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
_contentView.frame = self.bounds;
}
- (void)reload - (void)reload
{ {
[self loadBundle]; [self tearDown];
[self setUp];
} }
+ (void)reloadAll + (void)reloadAll
{ {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
object:self];
}
- (NSNumber *)reactTag
{
return _contentView.reactTag;
} }
- (void)startOrResetInteractionTiming - (void)startOrResetInteractionTiming
@ -350,3 +229,14 @@ static Class _globalExecutorClass;
} }
@end @end
@implementation RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag
{
NSNumber *rootTag = objc_getAssociatedObject(self, _cmd) ?: @1;
objc_setAssociatedObject(self, _cmd, @(rootTag.integerValue + 10), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return rootTag;
}
@end

View File

@ -9,7 +9,7 @@
#import <JavaScriptCore/JavaScriptCore.h> #import <JavaScriptCore/JavaScriptCore.h>
#import "RCTJavaScriptExecutor.h" #import "../Base/RCTJavaScriptExecutor.h"
// TODO (#5906496): Might RCTJSCoreExecutor be a better name for this? // TODO (#5906496): Might RCTJSCoreExecutor be a better name for this?

View File

@ -30,8 +30,7 @@
@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate; @property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
/** /**
* Register a root view with the RCTUIManager. Theoretically, a single manager * Register a root view with the RCTUIManager.
* can support multiple root views, however this feature is not currently exposed.
*/ */
- (void)registerRootView:(UIView *)rootView; - (void)registerRootView:(UIView *)rootView;

View File

@ -192,6 +192,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
NSMutableDictionary *_defaultShadowViews; // RCT thread only NSMutableDictionary *_defaultShadowViews; // RCT thread only
NSMutableDictionary *_defaultViews; // Main thread only NSMutableDictionary *_defaultViews; // Main thread only
NSDictionary *_viewManagers; NSDictionary *_viewManagers;
NSUInteger _rootTag;
} }
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@ -239,6 +240,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
// Internal resources // Internal resources
_pendingUIBlocks = [[NSMutableArray alloc] init]; _pendingUIBlocks = [[NSMutableArray alloc] init];
_rootViewTags = [[NSMutableSet alloc] init]; _rootViewTags = [[NSMutableSet alloc] init];
_rootTag = 1;
} }
return self; return self;
} }
@ -259,6 +261,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
_bridge = bridge; _bridge = bridge;
_shadowQueue = _bridge.shadowQueue; _shadowQueue = _bridge.shadowQueue;
_shadowViewRegistry = [[RCTSparseArray alloc] init];
// Get view managers from bridge // Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init]; NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];

View File

@ -39,6 +39,7 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; }; 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; }; 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; };
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; };
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; }; 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; }; 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
@ -146,6 +147,8 @@
13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = "<group>"; }; 13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = "<group>"; };
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; }; 13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; }; 13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; };
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; }; 14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; }; 14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; }; 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
@ -348,6 +351,8 @@
83CBBA491A601E3B00E9B192 /* Base */ = { 83CBBA491A601E3B00E9B192 /* Base */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */,
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */,
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */, 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */,
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */, 83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
@ -473,6 +478,7 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */, 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */, 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */, 13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */,
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */, 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */,
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */, 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */,
13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */, 13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */,