This commit is contained in:
Nick Lockwood 2015-04-11 15:08:00 -07:00
parent 699a9c3e0c
commit 26fd24dc50
21 changed files with 504 additions and 485 deletions

View File

@ -21,7 +21,7 @@
@interface RCTTestRunner : NSObject @interface RCTTestRunner : NSObject
@property (nonatomic, assign) BOOL recordMode; @property (nonatomic, assign) BOOL recordMode;
@property (nonatomic, copy) NSString *script; @property (nonatomic, strong) NSURL *scriptURL;
/** /**
* Initialize a runner. It's recommended that you use the initRunnerForApp macro instead of calling this directly. * Initialize a runner. It's recommended that you use the initRunnerForApp macro instead of calling this directly.
@ -55,7 +55,7 @@
* @param initialProps props that are passed into the component when rendered. * @param initialProps props that are passed into the component when rendered.
* @param expectErrorRegex A regex that must match the error thrown. If no error is thrown, the test fails. * @param expectErrorRegex A regex that must match the error thrown. If no error is thrown, the test fails.
*/ */
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)expectErrorRegex; - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSString *)expectErrorRegex;
/** /**
* Same as runTest:, but allows for passing initialProps for providing mock data or requesting different behaviors, and * Same as runTest:, but allows for passing initialProps for providing mock data or requesting different behaviors, and

View File

@ -29,7 +29,7 @@
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
_snapshotController.referenceImagesDirectory = referenceDir; _snapshotController.referenceImagesDirectory = referenceDir;
_script = [NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]; _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
} }
return self; return self;
} }
@ -49,10 +49,10 @@
[self runTest:test module:moduleName initialProps:nil expectErrorBlock:nil]; [self runTest:test module:moduleName initialProps:nil expectErrorBlock:nil];
} }
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)errorRegex - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSString *)errorRegex
{ {
[self runTest:test module:moduleName initialProps:initialProps expectErrorBlock:^BOOL(NSString *error){ [self runTest:test module:moduleName initialProps:initialProps expectErrorBlock:^BOOL(NSString *error){
return [errorRegex numberOfMatchesInString:error options:0 range:NSMakeRange(0, [error length])] > 0; return [error rangeOfString:errorRegex options:NSRegularExpressionSearch].location != NSNotFound;
}]; }];
} }
@ -66,11 +66,12 @@
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 RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:^(){ moduleProvider:^(){
return @[testModule]; return @[testModule];
} }
launchOptions:nil]; launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName]; moduleName:moduleName];
testModule.view = rootView; testModule.view = rootView;

View File

@ -11,6 +11,6 @@
@interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor> @interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor>
- (instancetype)initWithURL:(NSURL *)url; - (instancetype)initWithURL:(NSURL *)URL;
@end @end

View File

@ -10,6 +10,7 @@
#import "RCTWebSocketExecutor.h" #import "RCTWebSocketExecutor.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#import "SRWebSocket.h" #import "SRWebSocket.h"
@ -18,10 +19,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
@interface RCTWebSocketExecutor () <SRWebSocketDelegate> @interface RCTWebSocketExecutor () <SRWebSocketDelegate>
@end @end
@implementation RCTWebSocketExecutor { @implementation RCTWebSocketExecutor
{
SRWebSocket *_socket; SRWebSocket *_socket;
NSOperationQueue *_jsQueue; dispatch_queue_t _jsQueue;
NSMutableDictionary *_callbacks; RCTSparseArray *_callbacks;
dispatch_semaphore_t _socketOpenSemaphore; dispatch_semaphore_t _socketOpenSemaphore;
NSMutableDictionary *_injectedObjects; NSMutableDictionary *_injectedObjects;
} }
@ -31,23 +33,24 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]]; return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]];
} }
- (instancetype)initWithURL:(NSURL *)url - (instancetype)initWithURL:(NSURL *)URL
{ {
if (self = [super init]) { if (self = [super init]) {
_jsQueue = [[NSOperationQueue alloc] init];
_jsQueue.maxConcurrentOperationCount = 1; _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
_socket = [[SRWebSocket alloc] initWithURL:url]; _socket = [[SRWebSocket alloc] initWithURL:URL];
_socket.delegate = self; _socket.delegate = self;
_callbacks = [NSMutableDictionary dictionary]; _callbacks = [[RCTSparseArray alloc] init];
_injectedObjects = [NSMutableDictionary dictionary]; _injectedObjects = [[NSMutableDictionary alloc] init];
[_socket setDelegateOperationQueue:_jsQueue]; [_socket setDelegateDispatchQueue:_jsQueue];
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:URL];
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:url];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil];
if (![self connectToProxy]) { if (![self connectToProxy]) {
RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If you are running on the device check if you have the right IP address on `RCTWebSocketExecutor.m` file.", url); RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \
you are running on the device, check if you have the right IP \
address in `RCTWebSocketExecutor.m`.", URL);
[self invalidate]; [self invalidate];
return nil; return nil;
} }
@ -91,8 +94,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
{ {
NSError *error = nil; NSError *error = nil;
NSDictionary *reply = RCTJSONParse(message, &error); NSDictionary *reply = RCTJSONParse(message, &error);
NSUInteger messageID = [reply[@"replyID"] integerValue]; NSNumber *messageID = reply[@"replyID"];
WSMessageCallback callback = [_callbacks objectForKey:@(messageID)]; WSMessageCallback callback = _callbacks[messageID];
if (callback) { if (callback) {
callback(error, reply); callback(error, reply);
} }
@ -108,16 +111,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
RCTLogError(@"WebSocket connection failed with error %@", error); RCTLogError(@"WebSocket connection failed with error %@", error);
} }
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
}
- (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback - (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback
{ {
static NSUInteger lastID = 10000; static NSUInteger lastID = 10000;
[_jsQueue addOperationWithBlock:^{ dispatch_async(_jsQueue, ^{
if (!self.valid) { if (!self.valid) {
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{ NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
NSLocalizedDescriptionKey: @"socket closed" NSLocalizedDescriptionKey: @"socket closed"
@ -126,19 +124,17 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
return; return;
} }
NSUInteger expectedID = lastID++; NSNumber *expectedID = @(lastID++);
_callbacks[expectedID] = [callback copy];
_callbacks[@(expectedID)] = [callback copy];
NSMutableDictionary *messageWithID = [message mutableCopy]; NSMutableDictionary *messageWithID = [message mutableCopy];
messageWithID[@"id"] = @(expectedID); messageWithID[@"id"] = expectedID;
[_socket send:RCTJSONStringify(messageWithID, NULL)]; [_socket send:RCTJSONStringify(messageWithID, NULL)];
}]; });
} }
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete - (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
{ {
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [url absoluteString], @"inject": _injectedObjects}; NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [URL absoluteString], @"inject": _injectedObjects};
[self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) { [self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) {
onComplete(error); onComplete(error);
}]; }];
@ -147,7 +143,12 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
{ {
RCTAssert(onComplete != nil, @"callback was missing for exec JS call"); RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"moduleName": name, @"moduleMethod": method, @"arguments": arguments}; NSDictionary *message = @{
@"method": NSStringFromSelector(_cmd),
@"moduleName": name,
@"moduleMethod": method,
@"arguments": arguments
};
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) { [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {
if (socketError) { if (socketError) {
onComplete(nil, socketError); onComplete(nil, socketError);
@ -162,15 +163,14 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
- (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete
{ {
[_jsQueue addOperationWithBlock:^{ dispatch_async(_jsQueue, ^{
[_injectedObjects setObject:script forKey:objectName]; _injectedObjects[objectName] = script;
onComplete(nil); onComplete(nil);
}]; });
} }
- (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

@ -9,35 +9,73 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#define RCTErrorDomain @"RCTErrorDomain" #ifdef __cplusplus
extern "C" {
#endif
#define RCTAssert(condition, message, ...) _RCTAssert((condition) != 0, message, ##__VA_ARGS__) /**
#define RCTCAssert(condition, message, ...) _RCTCAssert((condition) != 0, message, ##__VA_ARGS__) * By default, only raise an NSAssertion in debug mode
* (custom assert functions will still be called).
*/
#ifndef RCT_ASSERT
#if DEBUG
#define RCT_ASSERT 1
#else
#define RCT_ASSERT 0
#endif
#endif
typedef void (^RCTAssertFunction)(BOOL condition, NSString *message, ...); /**
* The default error domain to be used for React errors.
*/
extern NSString *const RCTErrorDomain;
extern RCTAssertFunction RCTInjectedAssertFunction; /**
extern RCTAssertFunction RCTInjectedCAssertFunction; * A block signature to be used for custom assertion handling.
*/
typedef void (^RCTAssertFunction)(
BOOL condition,
NSString *fileName,
NSNumber *lineNumber,
NSString *function,
NSString *message
);
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction); /**
* Private logging function - ignore this.
*/
void _RCTAssertFormat(BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6);
#define _RCTAssert(condition, message, ...) \ /**
do { \ * This is the main assert macro that you should use.
if (RCTInjectedAssertFunction) { \ */
RCTInjectedAssertFunction(condition, message, ##__VA_ARGS__); \ #define RCTAssert(condition, ...) do { BOOL pass = ((condition) != 0); \
} else { \ if (RCT_ASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
NSAssert(condition, message, ##__VA_ARGS__); \ file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; } \
} \ _RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \
} while (false) } while (false)
#define _RCTCAssert(condition, message, ...) \ /**
do { \ * Convenience macro for asserting that we're running on main thread.
if (RCTInjectedCAssertFunction) { \ */
RCTInjectedCAssertFunction(condition, message, ##__VA_ARGS__); \ #define RCTAssertMainThread() RCTAssert([NSThread isMainThread], \
} else { \ @"This function must be called on the main thread");
NSCAssert(condition, message, ##__VA_ARGS__); \
} \
} while (false)
#define RCTAssertMainThread() RCTAssert([NSThread isMainThread], @"This method must be called on the main thread"); /**
#define RCTCAssertMainThread() RCTCAssert([NSThread isMainThread], @"This function must be called on the main thread"); * These methods get and set the current assert function called by the RCTAssert
* macros. You can use these to replace the standard behavior with custom log
* functionality.
*/
void RCTSetAssertFunction(RCTAssertFunction assertFunction);
RCTAssertFunction RCTGetAssertFunction(void);
/**
* This appends additional code to the existing assert function, without
* replacing the existing functionality. Useful if you just want to forward
* assert info to an extra service without changing the default behavior.
*/
void RCTAddAssertFunction(RCTAssertFunction assertFunction);
#ifdef __cplusplus
}
#endif

View File

@ -9,11 +9,54 @@
#import "RCTAssert.h" #import "RCTAssert.h"
RCTAssertFunction RCTInjectedAssertFunction = nil; NSString *const RCTErrorDomain = @"RCTErrorDomain";
RCTAssertFunction RCTInjectedCAssertFunction = nil;
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction) RCTAssertFunction RCTCurrentAssertFunction = nil;
void _RCTAssertFormat(
BOOL condition,
const char *fileName,
int lineNumber,
const char *function,
NSString *format, ...)
{ {
RCTInjectedAssertFunction = assertFunction; if (RCTCurrentAssertFunction) {
RCTInjectedCAssertFunction = cAssertFunction;
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
RCTCurrentAssertFunction(
condition, @(fileName), @(lineNumber), @(function), message
);
}
}
void RCTSetAssertFunction(RCTAssertFunction assertFunction)
{
RCTCurrentAssertFunction = assertFunction;
}
RCTAssertFunction RCTGetAssertFunction(void)
{
return RCTCurrentAssertFunction;
}
void RCTAddAssertFunction(RCTAssertFunction assertFunction)
{
RCTAssertFunction existing = RCTCurrentAssertFunction;
if (existing) {
RCTCurrentAssertFunction = ^(BOOL condition,
NSString *fileName,
NSNumber *lineNumber,
NSString *function,
NSString *message) {
existing(condition, fileName, lineNumber, function, message);
assertFunction(condition, fileName, lineNumber, function, message);
};
} else {
RCTCurrentAssertFunction = assertFunction;
}
} }

View File

@ -16,6 +16,16 @@
@class RCTBridge; @class RCTBridge;
@class RCTEventDispatcher; @class RCTEventDispatcher;
/**
* This notification triggers a reload of all bridges currently running.
*/
extern NSString *const RCTReloadNotification;
/**
* This notification fires when the bridge has finished loading.
*/
extern NSString *const RCTJavaScriptDidLoadNotification;
/** /**
* This block can be used to instantiate modules that require additional * This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used. * init parameters, or additional configuration prior to being used.
@ -44,9 +54,9 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
* array of pre-initialized module instances if they require additional init * array of pre-initialized module instances if they require additional init
* parameters or configuration. * parameters or configuration.
*/ */
- (instancetype)initWithBundlePath:(NSString *)bundlepath - (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
/** /**
* This method is used to call functions in the JavaScript application context. * This method is used to call functions in the JavaScript application context.
@ -105,7 +115,7 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
/** /**
* Use this to check if the bridge is currently loading. * Use this to check if the bridge is currently loading.
*/ */
@property (nonatomic, readonly, getter=isLoaded) BOOL loaded; @property (nonatomic, readonly, getter=isLoading) BOOL loading;
/** /**
* Reload the bundle and reset executor and modules. * Reload the bundle and reset executor and modules.

View File

@ -27,6 +27,9 @@
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
#import "RCTUtils.h" #import "RCTUtils.h"
NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
/** /**
* Must be kept in sync with `MessageQueue.js`. * Must be kept in sync with `MessageQueue.js`.
*/ */
@ -144,9 +147,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
// Get class // Get class
Class cls = NSClassFromString(moduleClassName); Class cls = NSClassFromString(moduleClassName);
RCTCAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol", @"%@ does not conform to the RCTBridgeModule protocol",
NSStringFromClass(cls)); NSStringFromClass(cls));
// Register module // Register module
[(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)]; [(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)];
@ -279,34 +282,34 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
// Get method signature // Get method signature
_methodSignature = _isClassMethod ? _methodSignature = _isClassMethod ?
[_moduleClass methodSignatureForSelector:_selector] : [_moduleClass methodSignatureForSelector:_selector] :
[_moduleClass instanceMethodSignatureForSelector:_selector]; [_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments // Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments; NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2]; NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#define RCT_ARG_BLOCK(_logic) \ #define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \ [argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \ _logic \
[invocation setArgument:&value atIndex:index]; \ [invocation setArgument:&value atIndex:index]; \
}]; \ }]; \
void (^addBlockArgument)(void) = ^{ void (^addBlockArgument)(void) = ^{
RCT_ARG_BLOCK( RCT_ARG_BLOCK(
if (json && ![json isKindOfClass:[NSNumber class]]) { if (json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return; return;
} }
// Marked as autoreleasing, because NSInvocation doesn't retain arguments // Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing id value = (json ? ^(NSArray *args) { __autoreleasing id value = (json ? ^(NSArray *args) {
[bridge _invokeAndProcessModule:@"BatchedBridge" [bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue" method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, args]]; arguments:@[json, args]];
} : ^(NSArray *unused) {}); } : ^(NSArray *unused) {});
) )
}; };
void (^defaultCase)(const char *) = ^(const char *argumentType) { void (^defaultCase)(const char *) = ^(const char *argumentType) {
@ -330,29 +333,29 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
switch (argumentType[0]) { switch (argumentType[0]) {
#define RCT_CONVERT_CASE(_value, _type) \ #define RCT_CONVERT_CASE(_value, _type) \
case _value: { \ case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \ _type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \ RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \ break; \
} }
RCT_CONVERT_CASE(':', SEL) RCT_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *) RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char) RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char) RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short) RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short) RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int) RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int) RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long) RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long) RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long) RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long) RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float) RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double) RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL) RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id) RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *) RCT_CONVERT_CASE('^', void *)
default: default:
defaultCase(argumentType); defaultCase(argumentType);
@ -368,47 +371,47 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
switch (argumentType[0]) { switch (argumentType[0]) {
#define RCT_CASE(_value, _class, _logic) \ #define RCT_CASE(_value, _class, _logic) \
case _value: { \ case _value: { \
RCT_ARG_BLOCK( \ RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \ if (json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \ RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \ json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \ return; \
} \ } \
_logic \ _logic \
) \ ) \
break; \ break; \
} }
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); ) RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
RCT_CASE('*', NSString, const char *value = [json UTF8String]; ) RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
#define RCT_SIMPLE_CASE(_value, _type, _selector) \ #define RCT_SIMPLE_CASE(_value, _type, _selector) \
case _value: { \ case _value: { \
RCT_ARG_BLOCK( \ RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \ if (json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \ RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \ index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \ return; \
} \ } \
_type value = [json _selector]; \ _type value = [json _selector]; \
) \ ) \
break; \ break; \
} }
RCT_SIMPLE_CASE('c', char, charValue) RCT_SIMPLE_CASE('c', char, charValue)
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue) RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
RCT_SIMPLE_CASE('s', short, shortValue) RCT_SIMPLE_CASE('s', short, shortValue)
RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue) RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
RCT_SIMPLE_CASE('i', int, intValue) RCT_SIMPLE_CASE('i', int, intValue)
RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue) RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
RCT_SIMPLE_CASE('l', long, longValue) RCT_SIMPLE_CASE('l', long, longValue)
RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue) RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
RCT_SIMPLE_CASE('q', long long, longLongValue) RCT_SIMPLE_CASE('q', long long, longLongValue)
RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue) RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
RCT_SIMPLE_CASE('f', float, floatValue) RCT_SIMPLE_CASE('f', float, floatValue)
RCT_SIMPLE_CASE('d', double, doubleValue) RCT_SIMPLE_CASE('d', double, doubleValue)
RCT_SIMPLE_CASE('B', BOOL, boolValue) RCT_SIMPLE_CASE('B', BOOL, boolValue)
default: default:
defaultCase(argumentType); defaultCase(argumentType);
@ -500,13 +503,13 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
// Create method // Create method
RCTModuleMethod *moduleMethod = RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithMethodName:@(entries[0]) [[RCTModuleMethod alloc] initWithMethodName:@(entries[0])
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil]; JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
// Cache method // Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName]; NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] = methodsByModuleClassName[moduleMethod.moduleClassName] =
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod]; methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
} }
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]]; methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]];
@ -557,15 +560,15 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count]; NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) { [methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
methodsByName[method.JSMethodName] = @{ methodsByName[method.JSMethodName] = @{
@"methodID": @(methodID), @"methodID": @(methodID),
@"type": @"remote", @"type": @"remote",
}; };
}]; }];
NSDictionary *module = @{ NSDictionary *module = @{
@"moduleID": @(moduleID), @"moduleID": @(moduleID),
@"methods": methodsByName @"methods": methodsByName
}; };
remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module; remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module;
}]; }];
@ -629,16 +632,16 @@ static NSDictionary *RCTLocalModulesConfig()
for (NSString *moduleDotMethod in RCTJSMethods()) { for (NSString *moduleDotMethod in RCTJSMethods()) {
NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."]; NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
RCTCAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod); RCTAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);
// Add module if it doesn't already exist // Add module if it doesn't already exist
NSString *moduleName = parts[0]; NSString *moduleName = parts[0];
NSDictionary *module = localModules[moduleName]; NSDictionary *module = localModules[moduleName];
if (!module) { if (!module) {
module = @{ module = @{
@"moduleID": @(localModules.count), @"moduleID": @(localModules.count),
@"methods": [[NSMutableDictionary alloc] init] @"methods": [[NSMutableDictionary alloc] init]
}; };
localModules[moduleName] = module; localModules[moduleName] = module;
} }
@ -647,9 +650,9 @@ static NSDictionary *RCTLocalModulesConfig()
NSMutableDictionary *methods = module[@"methods"]; NSMutableDictionary *methods = module[@"methods"];
if (!methods[methodName]) { if (!methods[methodName]) {
methods[methodName] = @{ methods[methodName] = @{
@"methodID": @(methods.count), @"methodID": @(methods.count),
@"type": @"local" @"type": @"local"
}; };
} }
// Add module and method lookup // Add module and method lookup
@ -667,22 +670,21 @@ static NSDictionary *RCTLocalModulesConfig()
NSDictionary *_modulesByName; NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor; id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass; Class _executorClass;
NSString *_bundlePath; NSURL *_bundleURL;
NSDictionary *_launchOptions;
RCTBridgeModuleProviderBlock _moduleProvider; RCTBridgeModuleProviderBlock _moduleProvider;
BOOL _loaded; BOOL _loading;
} }
static id<RCTJavaScriptExecutor> _latestJSExecutor; static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithBundlePath:(NSString *)bundlePath - (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions launchOptions:(NSDictionary *)launchOptions
{ {
if ((self = [super init])) { if ((self = [super init])) {
_bundlePath = bundlePath; _bundleURL = bundleURL;
_moduleProvider = block; _moduleProvider = block;
_launchOptions = launchOptions; _launchOptions = [launchOptions copy];
[self setUp]; [self setUp];
[self bindKeys]; [self bindKeys];
} }
@ -695,7 +697,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_javaScriptExecutor = [[executorClass alloc] init]; _javaScriptExecutor = [[executorClass alloc] init];
_latestJSExecutor = _javaScriptExecutor; _latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL); _shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Register passed-in module instances // Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
@ -743,21 +745,25 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
@"localModulesConfig": RCTLocalModulesConfig() @"localModulesConfig": RCTLocalModulesConfig()
}, NULL); }, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) { [_javaScriptExecutor injectJSONText:configJSON
dispatch_semaphore_signal(semaphore); asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
}]; dispatch_semaphore_signal(semaphore);
}];
_loading = YES;
if (_javaScriptExecutor == nil) { if (_javaScriptExecutor == nil) {
/** /**
* HACK (tadeu): If it failed to connect to the debugger, set loaded to true so we can * HACK (tadeu): If it failed to connect to the debugger, set loading to NO
* reload * so we can attempt to reload again.
*/ */
_loaded = YES; _loading = NO;
} else if (_bundlePath != nil) { // Allow testing without a script
} else if (_bundleURL) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:[NSURL URLWithString:_bundlePath] onComplete:^(NSError *error) { [loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) {
_loaded = YES; _loading = NO;
if (error != nil) { if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"]; NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) { if (stack) {
@ -770,6 +776,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
} else { } else {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self]; object:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
} }
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload) selector:@selector(reload)
@ -781,53 +792,56 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)bindKeys - (void)bindKeys
{ {
#if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
// Workaround around the first cmd+r not working: http://openradar.appspot.com/19613391 #if TARGET_IPHONE_SIMULATOR
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+r
__weak RCTBridge *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// Workaround around the first cmd+R not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+R
// will work like a charm! // will work like a charm!
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"" [commands registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) { action:NULL];
// Do nothing // reload in current mode
}]; [commands registerKeyCommandWithInput:@"r"
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand
modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) {
action:^(UIKeyCommand *command) { [weakSelf reload];
[weakSelf reload]; }];
}]; // reset to normal mode
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n" [commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) { action:^(UIKeyCommand *command) {
RCTBridge *strongSelf = weakSelf; __strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf) { strongSelf.executorClass = Nil;
return; [strongSelf reload];
} }];
strongSelf->_executorClass = Nil; // reload in debug mode
[strongSelf reload]; [commands registerKeyCommandWithInput:@"d"
}]; modifierFlags:UIKeyModifierCommand
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d" action:^(UIKeyCommand *command) {
modifierFlags:UIKeyModifierCommand __strong RCTBridge *strongSelf = weakSelf;
action:^(UIKeyCommand *command) { strongSelf.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
RCTBridge *strongSelf = weakSelf; if (!strongSelf.executorClass) {
if (!strongSelf) { strongSelf.executorClass = NSClassFromString(@"RCTWebViewExecutor");
return; }
} if (!strongSelf.executorClass) {
strongSelf->_executorClass = NSClassFromString(@"RCTWebSocketExecutor"); RCTLogError(@"WebSocket debugger is not available. "
if (!strongSelf->_executorClass) { "Did you forget to include RCTWebSocketExecutor?");
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?"); }
} [strongSelf reload];
[strongSelf reload]; }];
}];
#endif #endif
} }
- (NSDictionary *)modules - (NSDictionary *)modules
{ {
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. \ RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
You may be trying to access a module too early in the startup procedure."); "You may be trying to access a module too early in the startup procedure.");
return _modulesByName; return _modulesByName;
} }
@ -874,7 +888,6 @@ 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;
} }
/** /**
@ -898,10 +911,10 @@ 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) { if (!_loading) {
[self _invokeAndProcessModule:@"BatchedBridge" [self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue" method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]]; arguments:@[moduleID, methodID, args ?: @[]]];
} }
} }
@ -1051,20 +1064,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)reload - (void)reload
{ {
if (_loaded) { if (!_loading) {
// If the bridge has not loaded yet, the context will be already invalid at // If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed. // the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render. // It will crash the javascript, and even the next `load` won't render.
[self invalidate]; [self invalidate];
[self setUp]; [self setUp];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadViewsNotification
object:self];
} }
} }
+ (void)logMessage:(NSString *)message level:(NSString *)level + (void)logMessage:(NSString *)message level:(NSString *)level
{ {
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) { if (![_latestJSExecutor isValid]) {
return; return;
} }

View File

@ -38,8 +38,8 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
* match the Objective-C class name. * match the Objective-C class name.
*/ */
#define RCT_EXPORT_MODULE(js_name) \ #define RCT_EXPORT_MODULE(js_name) \
+ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \ + (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \
))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; } \ ))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; }
/** /**
* Place this macro inside the method body of any method you want to expose * Place this macro inside the method body of any method you want to expose
@ -59,17 +59,17 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
* a js_name argument and the exposed method will match the first part of the * a js_name argument and the exposed method will match the first part of the
* Objective-C method selector name (up to the first colon). * Objective-C method selector name (up to the first colon).
* *
* For example, in MyClass.m: * For example, in ModuleName.m:
* *
* - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b * - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b
* {} * { ... }
* *
* becomes * becomes
* *
* RCT_EXPORT_METHOD(doSomething:(NSString *)aString * RCT_EXPORT_METHOD(doSomething:(NSString *)aString
* withA:(NSInteger)a * withA:(NSInteger)a
* andB:(NSInteger)b) * andB:(NSInteger)b)
* {} * { ... }
* *
* and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`. * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
*/ */
@ -96,11 +96,3 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
- (void)batchDidComplete; - (void)batchDidComplete;
@end @end
#ifdef __cplusplus
extern "C" {
#endif
void RCTBridgeModuleRegisterClass(Class cls, NSString *moduleName);
#ifdef __cplusplus
}
#endif

View File

@ -14,15 +14,15 @@
#import "RCTSourceCode.h" #import "RCTSourceCode.h"
#import "RCTWebViewExecutor.h" #import "RCTWebViewExecutor.h"
@interface RCTDevMenu () <UIActionSheetDelegate> { @interface RCTDevMenu () <UIActionSheetDelegate>
BOOL _liveReload;
}
@property (nonatomic, weak) RCTBridge *bridge;
@end @end
@implementation RCTDevMenu @implementation RCTDevMenu
{
BOOL _liveReload;
__weak RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
{ {
@ -34,8 +34,8 @@
- (void)show - (void)show
{ {
NSString *debugTitleChrome = self.bridge.executorClass != Nil && self.bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = self.bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari 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
@ -49,15 +49,15 @@
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{ {
if (buttonIndex == 0) { if (buttonIndex == 0) {
[self.bridge reload]; [_bridge reload];
} else if (buttonIndex == 1) { } else if (buttonIndex == 1) {
Class cls = NSClassFromString(@"RCTWebSocketExecutor"); Class cls = NSClassFromString(@"RCTWebSocketExecutor");
self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : nil; _bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[self.bridge reload]; [_bridge reload];
} else if (buttonIndex == 2) { } else if (buttonIndex == 2) {
Class cls = [RCTWebViewExecutor class]; Class cls = [RCTWebViewExecutor class];
self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : Nil; _bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[self.bridge reload]; [_bridge reload];
} else if (buttonIndex == 3) { } else if (buttonIndex == 3) {
_liveReload = !_liveReload; _liveReload = !_liveReload;
[self _pollAndReload]; [self _pollAndReload];
@ -67,7 +67,7 @@
- (void)_pollAndReload - (void)_pollAndReload
{ {
if (_liveReload) { if (_liveReload) {
RCTSourceCode *sourceCodeModule = self.bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
NSURL *url = sourceCodeModule.scriptURL; 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];
@ -84,7 +84,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.bridge reload]; [_bridge reload];
} }
[self _pollAndReload]; [self _pollAndReload];
}); });

View File

@ -9,8 +9,6 @@
/** /**
* Class that allows easy embedding, loading, life-cycle management of a * Class that allows easy embedding, loading, life-cycle management of a
* JavaScript application inside of a native application. * 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). * TODO: Incremental module loading. (low pri).
*/ */
@interface RCTJavaScriptLoader : NSObject @interface RCTJavaScriptLoader : NSObject

View File

@ -46,8 +46,7 @@
*/ */
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
{ {
RCTAssertMainThread(); if ((self = [super init])) {
if (self = [super init]) {
_bridge = bridge; _bridge = bridge;
} }
return self; return self;
@ -56,12 +55,14 @@
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete - (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
{ {
if (scriptURL == nil) { if (scriptURL == nil) {
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
code:1 NSLocalizedDescriptionKey: @"No script URL provided"
userInfo:@{NSLocalizedDescriptionKey: @"No script URL provided"}]; }];
onComplete(error); onComplete(error);
return; return;
} else if ([scriptURL isFileURL]) { }
if ([scriptURL isFileURL]) {
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath]; NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length]; NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];

View File

@ -76,7 +76,8 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
// lookup seems to return nil sometimes, even if the key is found in the dictionary. // lookup seems to return nil sometimes, even if the key is found in the dictionary.
// To fix this, we use a linear search, since there won't be many keys anyway // To fix this, we use a linear search, since there won't be many keys anyway
[_commandBindings enumerateKeysAndObjectsUsingBlock:^(UIKeyCommand *k, void (^block)(UIKeyCommand *), BOOL *stop) { [_commandBindings enumerateKeysAndObjectsUsingBlock:
^(UIKeyCommand *k, void (^block)(UIKeyCommand *), BOOL *stop) {
if ([key.input isEqualToString:k.input] && key.modifierFlags == k.modifierFlags) { if ([key.input isEqualToString:k.input] && key.modifierFlags == k.modifierFlags) {
block(key); block(key);
} }
@ -92,10 +93,12 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
modifierFlags:flags modifierFlags:flags
action:@selector(RCT_handleKeyCommand:)]; action:@selector(RCT_handleKeyCommand:)];
_commandBindings[command] = block;
_commandBindings[command] = block ?: ^(UIKeyCommand *cmd) {};
} }
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags - (void)unregisterKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
{ {
RCTAssertMainThread(); RCTAssertMainThread();

View File

@ -23,12 +23,6 @@ extern "C" {
#define RCTLOG_FATAL_LEVEL RCTLogLevelMustFix #define RCTLOG_FATAL_LEVEL RCTLogLevelMustFix
#define RCTLOG_REDBOX_LEVEL RCTLogLevelError #define RCTLOG_REDBOX_LEVEL RCTLogLevelError
/**
* A regular expression that can be used to selectively limit the throwing of
* a exception to specific log contents.
*/
#define RCTLOG_FATAL_REGEX nil
/** /**
* An enum representing the severity of the log message. * An enum representing the severity of the log message.
*/ */
@ -104,24 +98,10 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
*/ */
void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5); void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
#define _RCTLog(lvl, ...) do { \ #define _RCTLog(lvl, ...) do { \
NSString *msg = [NSString stringWithFormat:__VA_ARGS__]; \ if (lvl >= RCTLOG_FATAL_LEVEL) { RCTAssert(NO, __VA_ARGS__); } \
if (lvl >= RCTLOG_FATAL_LEVEL) { \
BOOL fail = YES; \
if (RCTLOG_FATAL_REGEX) { \
if ([msg rangeOfString:RCTLOG_FATAL_REGEX options:NSRegularExpressionSearch].length) { \
fail = NO; \
} \
} \
RCTCAssert(!fail, @"FATAL ERROR: %@", msg); \
}\
_RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \ _RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \
} while (0) } while (0)
/**
* Legacy injection function - don't use this.
*/
void RCTInjectLogFunction(void (^)(NSString *msg));
/** /**
* Logging macros. Use these to log information, warnings and errors in your * Logging macros. Use these to log information, warnings and errors in your
* own code. * own code.

View File

@ -149,14 +149,25 @@ NSString *RCTFormatLog(
return log; return log;
} }
void _RCTLogFormat(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...) void _RCTLogFormat(
RCTLogLevel level,
const char *fileName,
int lineNumber,
NSString *format, ...)
{ {
if (RCTCurrentLogFunction && level >= RCTCurrentLogThreshold) {
#if DEBUG
BOOL log = YES;
#else
BOOL log = (RCTCurrentLogFunction != nil);
#endif
if (log && level >= RCTCurrentLogThreshold) {
// Get message // Get message
va_list args; va_list args;
va_start(args, format); va_start(args, format);
__block NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args); va_end(args);
// Add prefix // Add prefix
@ -186,26 +197,3 @@ void _RCTLogFormat(RCTLogLevel level, const char *fileName, int lineNumber, NSSt
} }
} }
#pragma mark - Deprecated
void RCTInjectLogFunction(void (^logFunction)(NSString *msg))
{
RCTSetLogFunction(^(RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
NSString *message) {
if (level > RCTLogLevelError) {
// Use custom log function
NSString *loc = fileName ? [NSString stringWithFormat:@"[%@:%@] ", fileName, lineNumber] : @"";
logFunction([loc stringByAppendingString:message]);
} else if (RCTDefaultLogFunction && level >= RCTCurrentLogThreshold) {
// Use default logger
RCTDefaultLogFunction(level, fileName, lineNumber, message);
}
});
}

View File

@ -9,6 +9,7 @@
#import "RCTRedBox.h" #import "RCTRedBox.h"
#import "RCTBridge.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource> @interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
@ -120,7 +121,7 @@
- (void)reload - (void)reload
{ {
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil userInfo:nil];
[self dismiss]; [self dismiss];
} }

View File

@ -11,10 +11,6 @@
#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>
/** /**
@ -68,16 +64,13 @@ extern NSString *const RCTReloadViewsNotification;
@property (nonatomic, assign) BOOL enableDevMenu; @property (nonatomic, assign) BOOL enableDevMenu;
/** /**
* Reload this root view, or all root views, respectively. * The backing view controller of the root view.
*/ */
- (void)reload;
+ (void)reloadAll;
@property (nonatomic, weak) UIViewController *backingViewController; @property (nonatomic, weak) UIViewController *backingViewController;
/**
* The React-managed contents view of the root view.
*/
@property (nonatomic, strong, readonly) UIView *contentView; @property (nonatomic, strong, readonly) UIView *contentView;
- (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming;
@end @end

View File

@ -24,10 +24,6 @@
#import "RCTWebViewExecutor.h" #import "RCTWebViewExecutor.h"
#import "UIView+React.h" #import "UIView+React.h"
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
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
* relying on it * relying on it
@ -50,7 +46,6 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
RCTBridge *_bridge; RCTBridge *_bridge;
RCTTouchHandler *_touchHandler; RCTTouchHandler *_touchHandler;
NSString *_moduleName; NSString *_moduleName;
BOOL _registered;
NSDictionary *_launchOptions; NSDictionary *_launchOptions;
UIView *_contentView; UIView *_contentView;
} }
@ -62,13 +57,26 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView"); RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
if ((self = [super init])) { if ((self = [super init])) {
self.backgroundColor = [UIColor whiteColor];
#ifdef DEBUG #ifdef DEBUG
_enableDevMenu = YES; _enableDevMenu = YES;
#endif #endif
_bridge = bridge; _bridge = bridge;
_moduleName = moduleName; _moduleName = moduleName;
self.backgroundColor = [UIColor whiteColor]; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self setUp];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
if (!_bridge.loading) {
[self bundleFinishedLoading];
}
} }
return self; return self;
} }
@ -77,57 +85,29 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
moduleName:(NSString *)moduleName moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions launchOptions:(NSDictionary *)launchOptions
{ {
RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:bundleURL.absoluteString RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
moduleProvider:nil moduleProvider:nil
launchOptions:launchOptions]; launchOptions:launchOptions];
return [self initWithBridge:bridge
moduleName:moduleName];
}
- (void)dealloc return [self initWithBridge:bridge moduleName:moduleName];
{
[self tearDown];
}
- (void)setUp
{
if (!_registered) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadViewsNotification
object:_bridge];
if (_bridge.loaded) {
[self bundleFinishedLoading];
} else {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
}
}
}
- (void)tearDown
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_registered) {
_registered = NO;
[_contentView removeGestureRecognizer:_touchHandler];
[_contentView removeFromSuperview];
[_touchHandler invalidate];
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[_contentView.reactTag]];
}
} }
- (BOOL)isValid - (BOOL)isValid
{ {
return _registered; return _contentView.userInteractionEnabled;
} }
- (void)invalidate - (void)invalidate
{ {
[self tearDown]; _contentView.userInteractionEnabled = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_touchHandler invalidate];
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[_contentView.reactTag]];
} }
- (UIViewController *)backingViewController { - (UIViewController *)backingViewController {
@ -143,7 +123,7 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
{ {
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) { if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
if (!_devMenu) { if (!_devMenu) {
_devMenu = [[RCTDevMenu alloc] initWithBridge:self.bridge]; _devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge];
} }
[_devMenu show]; [_devMenu show];
} }
@ -155,7 +135,7 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
- (void)bundleFinishedLoading - (void)bundleFinishedLoading
{ {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
_registered = YES;
/** /**
* Every root view that is created must have a unique react tag. * Every root view that is created must have a unique react tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc * Numbering of these tags goes from 1, 11, 21, 31, etc
@ -163,6 +143,8 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
* NOTE: Since the bridge persists, the RootViews might be reused, so now * NOTE: Since the bridge persists, the RootViews might be reused, so now
* the react tag is assigned every time we load new content. * the react tag is assigned every time we load new content.
*/ */
[_touchHandler invalidate];
[_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds]; _contentView = [[UIView alloc] initWithFrame:self.bounds];
_contentView.reactTag = [_bridge.uiManager allocateRootTag]; _contentView.reactTag = [_bridge.uiManager allocateRootTag];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
@ -183,45 +165,17 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
- (void)layoutSubviews - (void)layoutSubviews
{ {
[super layoutSubviews]; [super layoutSubviews];
_contentView.frame = self.bounds; if (_contentView) {
if (_registered) { _contentView.frame = self.bounds;
[_bridge.uiManager setFrame:self.frame forRootView:_contentView]; [_bridge.uiManager setFrame:self.frame forRootView:_contentView];
} }
} }
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
_contentView.frame = self.bounds;
}
- (void)reload
{
[self tearDown];
[self setUp];
}
+ (void)reloadAll
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
object:self];
}
- (NSNumber *)reactTag - (NSNumber *)reactTag
{ {
return _contentView.reactTag; return _contentView.reactTag;
} }
- (void)startOrResetInteractionTiming
{
[_touchHandler startOrResetInteractionTiming];
}
- (NSDictionary *)endAndResetInteractionTiming
{
return [_touchHandler endAndResetInteractionTiming];
}
@end @end
@implementation RCTUIManager (RCTRootView) @implementation RCTUIManager (RCTRootView)

View File

@ -37,7 +37,7 @@
+ (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime + (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime
{ {
RCTTouchEvent *touchEvent = [[self alloc] init]; RCTTouchEvent *touchEvent = [[self alloc] init];
touchEvent->_id = [self newID]; touchEvent->_id = [self newTaskID];
touchEvent->_eventName = [eventName copy]; touchEvent->_eventName = [eventName copy];
touchEvent->_touches = [touches copy]; touchEvent->_touches = [touches copy];
touchEvent->_changedIndexes = [changedIndexes copy]; touchEvent->_changedIndexes = [changedIndexes copy];
@ -45,10 +45,10 @@
return touchEvent; return touchEvent;
} }
+ (NSUInteger)newID + (NSUInteger)newTaskID
{ {
static NSUInteger id = 0; static NSUInteger taskID = 0;
return ++id; return ++taskID;
} }
@end @end
@ -282,7 +282,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
[_bridgeInteractionTiming addObject:@{ [_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp), @"timeSeconds": @(sender.timestamp),
@"operation": @"mainThreadDisplayLink", @"operation": @"mainThreadDisplayLink",
@"taskID": @([RCTTouchEvent newID]), @"taskID": @([RCTTouchEvent newTaskID]),
}]; }];
} }
} }

View File

@ -42,12 +42,9 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
- (instancetype)initWithWebView:(UIWebView *)webView - (instancetype)initWithWebView:(UIWebView *)webView
{ {
if (!webView) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Can't init with a nil webview" userInfo:nil];
}
if ((self = [super init])) { if ((self = [super init])) {
_objectsToInject = [[NSMutableDictionary alloc] init]; _objectsToInject = [[NSMutableDictionary alloc] init];
_webView = webView; _webView = webView ?: [[UIWebView alloc] init];
_webView.delegate = self; _webView.delegate = self;
} }
return self; return self;
@ -55,7 +52,7 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
- (id)init - (id)init
{ {
return [self initWithWebView:[[UIWebView alloc] init]]; return [self initWithWebView:nil];
} }
- (BOOL)isValid - (BOOL)isValid

View File

@ -23,6 +23,7 @@
#import "RCTScrollableProtocol.h" #import "RCTScrollableProtocol.h"
#import "RCTShadowView.h" #import "RCTShadowView.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
#import "RCTTouchHandler.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#import "RCTView.h" #import "RCTView.h"
#import "RCTViewManager.h" #import "RCTViewManager.h"
@ -211,7 +212,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
static NSString *RCTViewNameForModuleName(NSString *moduleName) static NSString *RCTViewNameForModuleName(NSString *moduleName)
{ {
NSString *name = moduleName; NSString *name = moduleName;
RCTCAssert(name.length, @"Invalid moduleName '%@'", moduleName); RCTAssert(name.length, @"Invalid moduleName '%@'", moduleName);
if ([name hasSuffix:@"Manager"]) { if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length]; name = [name substringToIndex:name.length - @"Manager".length];
} }
@ -258,31 +259,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
RCTAssert(!self.valid, @"must call -invalidate before -dealloc"); RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
} }
- (void)setBridge:(RCTBridge *)bridge
{
if (_bridge) {
// Clear previous bridge data
[self invalidate];
}
if (bridge) {
_bridge = bridge;
_shadowQueue = _bridge.shadowQueue;
_shadowViewRegistry = [[RCTSparseArray alloc] init];
// Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) {
if ([manager isKindOfClass:[RCTViewManager class]]) {
viewManagers[RCTViewNameForModuleName(moduleName)] = manager;
}
}];
_viewManagers = [viewManagers copy];
}
}
- (BOOL)isValid - (BOOL)isValid
{ {
return _viewRegistry != nil; return _viewRegistry != nil;
@ -292,8 +268,13 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
{ {
RCTAssertMainThread(); RCTAssertMainThread();
_viewRegistry = nil; for (NSNumber *rootViewTag in _rootViewTags) {
((UIView *)_viewRegistry[rootViewTag]).userInteractionEnabled = NO;
}
_rootViewTags = nil;
_shadowViewRegistry = nil; _shadowViewRegistry = nil;
_viewRegistry = nil;
_bridge = nil; _bridge = nil;
[_pendingUIBlocksLock lock]; [_pendingUIBlocksLock lock];
@ -301,6 +282,25 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
[_pendingUIBlocksLock unlock]; [_pendingUIBlocksLock unlock];
} }
- (void)setBridge:(RCTBridge *)bridge
{
RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance");
_bridge = bridge;
_shadowQueue = _bridge.shadowQueue;
_shadowViewRegistry = [[RCTSparseArray alloc] init];
// Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) {
if ([manager isKindOfClass:[RCTViewManager class]]) {
viewManagers[RCTViewNameForModuleName(moduleName)] = manager;
}
}];
_viewManagers = [viewManagers copy];
}
- (void)registerRootView:(UIView *)rootView; - (void)registerRootView:(UIView *)rootView;
{ {
RCTAssertMainThread(); RCTAssertMainThread();
@ -310,8 +310,8 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
@"View %@ with tag #%@ is not a root view", rootView, reactTag); @"View %@ with tag #%@ is not a root view", rootView, reactTag);
UIView *existingView = _viewRegistry[reactTag]; UIView *existingView = _viewRegistry[reactTag];
RCTCAssert(existingView == nil || existingView == rootView, RCTAssert(existingView == nil || existingView == rootView,
@"Expect all root views to have unique tag. Added %@ twice", reactTag); @"Expect all root views to have unique tag. Added %@ twice", reactTag);
// Register view // Register view
_viewRegistry[reactTag] = rootView; _viewRegistry[reactTag] = rootView;
@ -548,7 +548,7 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(NSNumber *)containerID)
} }
// Construction of removed children must be done "up front", before indices are disturbed by removals. // Construction of removed children must be done "up front", before indices are disturbed by removals.
NSMutableArray *removedChildren = [NSMutableArray arrayWithCapacity:atIndices.count]; NSMutableArray *removedChildren = [NSMutableArray arrayWithCapacity:atIndices.count];
RCTCAssert(container != nil, @"container view (for ID %@) not found", container); RCTAssert(container != nil, @"container view (for ID %@) not found", container);
for (NSInteger i = 0; i < [atIndices count]; i++) { for (NSInteger i = 0; i < [atIndices count]; i++) {
NSInteger index = [atIndices[i] integerValue]; NSInteger index = [atIndices[i] integerValue];
if (index < [[container reactSubviews] count]) { if (index < [[container reactSubviews] count]) {
@ -577,7 +577,7 @@ RCT_EXPORT_METHOD(removeRootView:(NSNumber *)rootReactTag)
[_rootViewTags removeObject:rootReactTag]; [_rootViewTags removeObject:rootReactTag];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTCAssertMainThread(); RCTAssertMainThread();
UIView *rootView = viewRegistry[rootReactTag]; UIView *rootView = viewRegistry[rootReactTag];
[uiManager _purgeChildren:rootView.reactSubviews fromRegistry:viewRegistry]; [uiManager _purgeChildren:rootView.reactSubviews fromRegistry:viewRegistry];
viewRegistry[rootReactTag] = nil; viewRegistry[rootReactTag] = nil;
@ -666,7 +666,7 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
NSArray *sortedIndices = [[destinationsToChildrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; NSArray *sortedIndices = [[destinationsToChildrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)];
for (NSNumber *reactIndex in sortedIndices) { for (NSNumber *reactIndex in sortedIndices) {
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:[reactIndex integerValue]]; [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
} }
} }
@ -757,7 +757,7 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
_shadowViewRegistry[reactTag] = shadowView; _shadowViewRegistry[reactTag] = shadowView;
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTCAssertMainThread(); RCTAssertMainThread();
UIView *view = [manager view]; UIView *view = [manager view];
if (view) { if (view) {
@ -782,6 +782,7 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
viewRegistry[reactTag] = view; viewRegistry[reactTag] = view;
}]; }];
} }
// TODO: remove viewName param as it isn't needed // TODO: remove viewName param as it isn't needed
RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag
viewName:(__unused NSString *)_ viewName:(__unused NSString *)_
@ -898,7 +899,7 @@ RCT_EXPORT_METHOD(measure:(NSNumber *)reactTag
} }
// TODO: this doesn't work because sometimes view is inside a modal window // TODO: this doesn't work because sometimes view is inside a modal window
// RCTCAssert([rootView isReactRootView], @"React view is not inside a react root view"); // RCTAssert([rootView isReactRootView], @"React view is not inside a react root view");
// By convention, all coordinates, whether they be touch coordinates, or // By convention, all coordinates, whether they be touch coordinates, or
// measurement coordinates are with respect to the root view. // measurement coordinates are with respect to the root view.
@ -1036,12 +1037,12 @@ RCT_EXPORT_METHOD(setMainScrollViewTag:(NSNumber *)reactTag)
uiManager.mainScrollView.nativeMainScrollDelegate = nil; uiManager.mainScrollView.nativeMainScrollDelegate = nil;
} }
if (reactTag) { if (reactTag) {
id rkObject = viewRegistry[reactTag]; id view = viewRegistry[reactTag];
if ([rkObject conformsToProtocol:@protocol(RCTScrollableProtocol)]) { if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
uiManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject; uiManager.mainScrollView = (id<RCTScrollableProtocol>)view;
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate; uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
} else { } else {
RCTCAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag); RCTAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
} }
} else { } else {
uiManager.mainScrollView = nil; uiManager.mainScrollView = nil;
@ -1049,28 +1050,30 @@ RCT_EXPORT_METHOD(setMainScrollViewTag:(NSNumber *)reactTag)
}]; }];
} }
// TODO: we could just pass point property
RCT_EXPORT_METHOD(scrollTo:(NSNumber *)reactTag RCT_EXPORT_METHOD(scrollTo:(NSNumber *)reactTag
withOffsetX:(NSNumber *)offsetX withOffsetX:(CGFloat)offsetX
offsetY:(NSNumber *)offsetY) offsetY:(CGFloat)offsetY)
{ {
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
UIView *view = viewRegistry[reactTag]; UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:YES]; [(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:YES];
} else { } else {
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
} }
}]; }];
} }
// TODO: we could just pass point property
RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag
offsetX:(NSNumber *)offsetX offsetX:(CGFloat)offsetX
offsetY:(NSNumber *)offsetY) offsetY:(CGFloat)offsetY)
{ {
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
UIView *view = viewRegistry[reactTag]; UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:NO]; [(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:NO];
} else { } else {
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
} }
@ -1078,12 +1081,12 @@ RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag
} }
RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
withRect:(NSDictionary *)rectDict) withRect:(CGRect)rect)
{ {
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
UIView *view = viewRegistry[reactTag]; UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES]; [(id<RCTScrollableProtocol>)view zoomToRect:rect animated:YES];
} else { } else {
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
} }
@ -1209,8 +1212,8 @@ RCT_EXPORT_METHOD(clearJSResponder)
if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) { if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) {
NSDictionary *eventTypes = [manager customBubblingEventTypes]; NSDictionary *eventTypes = [manager customBubblingEventTypes];
for (NSString *eventName in eventTypes) { for (NSString *eventName in eventTypes) {
RCTCAssert(!customBubblingEventTypesConfigs[eventName], RCTAssert(!customBubblingEventTypesConfigs[eventName],
@"Event '%@' registered multiple times.", eventName); @"Event '%@' registered multiple times.", eventName);
} }
[customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes]; [customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
} }
@ -1261,7 +1264,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) { if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
NSDictionary *eventTypes = [manager customDirectEventTypes]; NSDictionary *eventTypes = [manager customDirectEventTypes];
for (NSString *eventName in eventTypes) { for (NSString *eventName in eventTypes) {
RCTCAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName); RCTAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
} }
[customDirectEventTypes addEntriesFromDictionary:eventTypes]; [customDirectEventTypes addEntriesFromDictionary:eventTypes];
} }
@ -1395,9 +1398,12 @@ RCT_EXPORT_METHOD(startOrResetInteractionTiming)
NSSet *rootViewTags = [_rootViewTags copy]; NSSet *rootViewTags = [_rootViewTags copy];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (NSNumber *reactTag in rootViewTags) { for (NSNumber *reactTag in rootViewTags) {
id rootView = viewRegistry[reactTag]; UIView *rootView = viewRegistry[reactTag];
if ([rootView respondsToSelector:@selector(startOrResetInteractionTiming)]) { for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
[rootView startOrResetInteractionTiming]; if ([handler isKindOfClass:[RCTTouchHandler class]]) {
[handler startOrResetInteractionTiming];
break;
}
} }
} }
}]; }];
@ -1410,9 +1416,12 @@ RCT_EXPORT_METHOD(endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init]; NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init];
for (NSNumber *reactTag in rootViewTags) { for (NSNumber *reactTag in rootViewTags) {
id rootView = viewRegistry[reactTag]; UIView *rootView = viewRegistry[reactTag];
if ([rootView respondsToSelector:@selector(endAndResetInteractionTiming)]) { for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
timingData[reactTag.stringValue] = [rootView endAndResetInteractionTiming]; if ([handler isKindOfClass:[RCTTouchHandler class]]) {
[handler endAndResetInteractionTiming];
break;
}
} }
} }
onSuccess(@[timingData]); onSuccess(@[timingData]);