Merge branch 'oss-sync/master' into HEAD

This commit is contained in:
Christopher Chedeau
2015-04-14 18:16:23 -07:00
44 changed files with 1403 additions and 688 deletions

View File

@@ -9,35 +9,73 @@
#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 { \
if (RCTInjectedAssertFunction) { \
RCTInjectedAssertFunction(condition, message, ##__VA_ARGS__); \
} else { \
NSAssert(condition, message, ##__VA_ARGS__); \
} \
/**
* This is the main assert macro that you should use.
*/
#define RCTAssert(condition, ...) do { BOOL pass = ((condition) != 0); \
if (RCT_ASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; } \
_RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \
} while (false)
#define _RCTCAssert(condition, message, ...) \
do { \
if (RCTInjectedCAssertFunction) { \
RCTInjectedCAssertFunction(condition, message, ##__VA_ARGS__); \
} else { \
NSCAssert(condition, message, ##__VA_ARGS__); \
} \
} while (false)
/**
* Convenience macro for asserting that we're running on main thread.
*/
#define RCTAssertMainThread() RCTAssert([NSThread isMainThread], \
@"This function must be called on the main thread");
#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"
RCTAssertFunction RCTInjectedAssertFunction = nil;
RCTAssertFunction RCTInjectedCAssertFunction = nil;
NSString *const RCTErrorDomain = @"RCTErrorDomain";
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;
RCTInjectedCAssertFunction = cAssertFunction;
if (RCTCurrentAssertFunction) {
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 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
* 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
* parameters or configuration.
*/
- (instancetype)initWithBundlePath:(NSString *)bundlepath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
/**
* 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.
*/
@property (nonatomic, readonly, getter=isLoaded) BOOL loaded;
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
/**
* Reload the bundle and reset executor and modules.

View File

@@ -27,6 +27,9 @@
#import "RCTSparseArray.h"
#import "RCTUtils.h"
NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
/**
* Must be kept in sync with `MessageQueue.js`.
*/
@@ -144,9 +147,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
// Get class
Class cls = NSClassFromString(moduleClassName);
RCTCAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
NSStringFromClass(cls));
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
NSStringFromClass(cls));
// Register module
[(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)];
@@ -216,7 +219,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
static Class _globalExecutorClass;
NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
static NSString *RCTStringUpToFirstArgument(NSString *methodName)
{
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.length) {
methodName = [methodName substringToIndex:colonRange.location];
@@ -224,12 +228,13 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
return methodName;
}
- (instancetype)initWithMethodName:(NSString *)methodName
JSMethodName:(NSString *)JSMethodName
- (instancetype)initWithReactMethodName:(NSString *)reactMethodName
objCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName
{
if ((self = [super init])) {
_methodName = methodName;
NSArray *parts = [[methodName substringWithRange:(NSRange){2, methodName.length - 3}] componentsSeparatedByString:@" "];
_methodName = reactMethodName;
NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "];
// Parse class and method
_moduleClassName = parts[0];
@@ -243,7 +248,7 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
// New format
NSString *selectorString = [parts[1] substringFromIndex:14];
_selector = NSSelectorFromString(selectorString);
_JSMethodName = RCTStringUpToFirstArgument(selectorString);
_JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString);
static NSRegularExpression *regExp;
if (!regExp) {
@@ -255,8 +260,8 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
}
argumentNames = [NSMutableArray array];
[regExp enumerateMatchesInString:JSMethodName options:0 range:NSMakeRange(0, JSMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSString *argumentName = [JSMethodName substringWithRange:[result rangeAtIndex:1]];
[regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
[(NSMutableArray *)argumentNames addObject:argumentName];
}];
} else {
@@ -267,30 +272,31 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
}
// Extract class and method details
_isClassMethod = [methodName characterAtIndex:0] == '+';
_isClassMethod = [reactMethodName characterAtIndex:0] == '+';
_moduleClass = NSClassFromString(_moduleClassName);
#if DEBUG
// Sanity check
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"You are attempting to export the method %@, but %@ does not \
conform to the RCTBridgeModule Protocol", methodName, _moduleClassName);
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
#endif
// Get method signature
_methodSignature = _isClassMethod ?
[_moduleClass methodSignatureForSelector:_selector] :
[_moduleClass instanceMethodSignatureForSelector:_selector];
[_moduleClass methodSignatureForSelector:_selector] :
[_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
}]; \
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
}]; \
void (^addBlockArgument)(void) = ^{
RCT_ARG_BLOCK(
@@ -330,29 +336,29 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
switch (argumentType[0]) {
#define RCT_CONVERT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
RCT_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
RCT_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
default:
defaultCase(argumentType);
@@ -368,47 +374,47 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
switch (argumentType[0]) {
#define RCT_CASE(_value, _class, _logic) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
} \
_logic \
) \
break; \
}
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
} \
_logic \
) \
break; \
}
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
} \
_type value = [json _selector]; \
) \
break; \
}
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
} \
_type value = [json _selector]; \
) \
break; \
}
RCT_SIMPLE_CASE('c', char, charValue)
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
RCT_SIMPLE_CASE('s', short, shortValue)
RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
RCT_SIMPLE_CASE('i', int, intValue)
RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
RCT_SIMPLE_CASE('l', long, longValue)
RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
RCT_SIMPLE_CASE('q', long long, longLongValue)
RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
RCT_SIMPLE_CASE('f', float, floatValue)
RCT_SIMPLE_CASE('d', double, doubleValue)
RCT_SIMPLE_CASE('B', BOOL, boolValue)
RCT_SIMPLE_CASE('c', char, charValue)
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
RCT_SIMPLE_CASE('s', short, shortValue)
RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
RCT_SIMPLE_CASE('i', int, intValue)
RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
RCT_SIMPLE_CASE('l', long, longValue)
RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
RCT_SIMPLE_CASE('q', long long, longLongValue)
RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
RCT_SIMPLE_CASE('f', float, floatValue)
RCT_SIMPLE_CASE('d', double, doubleValue)
RCT_SIMPLE_CASE('B', BOOL, boolValue)
default:
defaultCase(argumentType);
@@ -429,6 +435,7 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
{
#if DEBUG
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", _methodName, [module class]);
@@ -493,20 +500,29 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
for (RCTHeaderValue addr = section->offset;
addr < section->offset + section->size;
addr += sizeof(const char **) * 2) {
addr += sizeof(const char **) * 3) {
// Get data entry
const char **entries = (const char **)(mach_header + addr);
// Create method
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithMethodName:@(entries[0])
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
RCTModuleMethod *moduleMethod;
if (entries[2] == NULL) {
// Legacy support for RCT_EXPORT()
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
objCMethodName:@(entries[0])
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
} else {
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil
JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
}
// Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] =
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
}
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]];
@@ -629,7 +645,7 @@ static NSDictionary *RCTLocalModulesConfig()
for (NSString *moduleDotMethod in RCTJSMethods()) {
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
NSString *moduleName = parts[0];
@@ -667,22 +683,21 @@ static NSDictionary *RCTLocalModulesConfig()
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSString *_bundlePath;
NSDictionary *_launchOptions;
NSURL *_bundleURL;
RCTBridgeModuleProviderBlock _moduleProvider;
BOOL _loaded;
BOOL _loading;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithBundlePath:(NSString *)bundlePath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
if ((self = [super init])) {
_bundlePath = bundlePath;
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = launchOptions;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
}
@@ -695,7 +710,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_javaScriptExecutor = [[executorClass alloc] init];
_latestJSExecutor = _javaScriptExecutor;
_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
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
@@ -739,25 +754,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
@"localModulesConfig": RCTLocalModulesConfig()
}, NULL);
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
@"localModulesConfig": RCTLocalModulesConfig()
}, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
dispatch_semaphore_signal(semaphore);
}];
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
dispatch_semaphore_signal(semaphore);
}];
_loading = YES;
if (_javaScriptExecutor == nil) {
/**
* HACK (tadeu): If it failed to connect to the debugger, set loaded to true so we can
* reload
* HACK (tadeu): If it failed to connect to the debugger, set loading to NO
* so we can attempt to reload again.
*/
_loaded = YES;
} else if (_bundlePath != nil) { // Allow testing without a script
_loading = NO;
} else if (_bundleURL) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:[NSURL URLWithString:_bundlePath] onComplete:^(NSError *error) {
_loaded = YES;
[loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) {
_loading = NO;
if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
@@ -769,72 +788,80 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
object:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}];
}
}
- (void)bindKeys
{
#if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
// 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
#if TARGET_IPHONE_SIMULATOR
__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!
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
// Do nothing
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
RCTBridge *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_executorClass = Nil;
[strongSelf reload];
}];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
RCTBridge *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!strongSelf->_executorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
[strongSelf reload];
}];
[commands registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand
action:NULL];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
// reset to normal mode
[commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTBridge *strongSelf = weakSelf;
strongSelf.executorClass = Nil;
[strongSelf reload];
}];
// reload in debug mode
[commands registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTBridge *strongSelf = weakSelf;
strongSelf.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!strongSelf.executorClass) {
strongSelf.executorClass = NSClassFromString(@"RCTWebViewExecutor");
}
if (!strongSelf.executorClass) {
RCTLogError(@"WebSocket debugger is not available. "
"Did you forget to include RCTWebSocketExecutor?");
}
[strongSelf reload];
}];
#endif
}
- (NSDictionary *)modules
{
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. \
You may be trying to access a module too early in the startup procedure.");
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
"You may be trying to access a module too early in the startup procedure.");
return _modulesByName;
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
[self invalidate];
}
#pragma mark - RCTInvalidating
@@ -846,12 +873,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)invalidate
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (!self.isValid && _modulesByID == nil) {
return;
}
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
return;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Release executor
if (_latestJSExecutor == _javaScriptExecutor) {
@@ -860,11 +891,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});
// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
@@ -875,7 +901,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_modulesByName = nil;
_loaded = NO;
}
/**
@@ -899,10 +924,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (self.loaded) {
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]];
if (!_loading) {
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]];
}
}
@@ -1052,20 +1077,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)reload
{
if (_loaded) {
if (!_loading) {
// 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];
}
}
+ (void)logMessage:(NSString *)message level:(NSString *)level
{
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
if (![_latestJSExecutor isValid]) {
return;
}

View File

@@ -29,57 +29,66 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add @synthesize bridge = _bridge;
*/
@property (nonatomic, strong) RCTBridge *bridge;
@property (nonatomic, weak) RCTBridge *bridge;
/**
* Place this macro in your class implementation, to automatically register
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_name argument
* will be used as the JS module name. If omitted, the JS module name will
* match the Objective-C class name.
*/
#define RCT_EXPORT_MODULE(js_name) \
+ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \
))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; } \
/**
* Place this macro inside the method body of any method you want to expose
* to JS. The optional js_name argument will be used as the JS method name
* (the method will be namespaced to the module name, as specified above).
* If omitted, the JS method name will match the first part of the Objective-C
* method selector name (up to the first colon).
*/
#define RCT_EXPORT(js_name) \
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
__attribute__((used, section("__DATA,RCTExport"))) \
static const char *__rct_export_entry__[] = { __func__, #js_name }
+ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \
))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; }
/**
* Wrap the parameter line of your method implementation with this macro to
* expose it to JS. Unlike the deprecated RCT_EXPORT, this macro does not take
* 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).
* expose it to JS. By default the exposed method will match the first part of
* the Objective-C method selector name (up to the first colon). Use
* RCT_REMAP_METHOD to specify the JS name of the method.
*
* For example, in MyClass.m:
* For example, in ModuleName.m:
*
* - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b
* {}
* { ... }
*
* becomes
*
* RCT_EXPORT_METHOD(doSomething:(NSString *)aString
* withA:(NSInteger)a
* andB:(NSInteger)b)
* {}
* { ... }
*
* and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
*/
#define RCT_EXPORT_METHOD(method) \
RCT_REMAP_METHOD(, method)
/**
* Similar to RCT_EXPORT_METHOD but lets you set the JS name of the exported
* method. Example usage:
*
* RCT_REMAP_METHOD(executeQueryWithParameters,
* executeQuery:(NSString *)query parameters:(NSDictionary *)parameters)
* { ... }
*/
#define RCT_REMAP_METHOD(js_name, method) \
- (void)__rct_export__##method { \
__attribute__((used, section("__DATA,RCTExport"))) \
static const char *__rct_export_entry__[] = { __func__, #method }; \
__attribute__((__aligned__(1))) \
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
} \
- (void)method
/**
* Deprecated, do not use.
*/
#define RCT_EXPORT(js_name) \
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
__attribute__((used, section("__DATA,RCTExport"))) \
__attribute__((__aligned__(1))) \
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.ModuleName.X. This method is called when the module is
@@ -96,11 +105,3 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
- (void)batchDidComplete;
@end
#ifdef __cplusplus
extern "C" {
#endif
void RCTBridgeModuleRegisterClass(Class cls, NSString *moduleName);
#ifdef __cplusplus
}
#endif

View File

@@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <objc/message.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@@ -133,6 +135,11 @@ BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
*/
BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
/**
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
*/
NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id);
#ifdef __cplusplus
}
#endif
@@ -167,7 +174,7 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
/**
* This macro is similar to RCT_CONVERTER, but specifically geared towards
* numeric types. It will handle string input correctly, and provides more
* detailed error reporting if a wrong value is passed in.
* detailed error reporting if an invalid value is passed in.
*/
#define RCT_NUMBER_CONVERTER(type, getter) \
RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
@@ -183,25 +190,8 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
dispatch_once(&onceToken, ^{ \
mapping = values; \
}); \
if (!json || json == [NSNull null]) { \
return default; \
} \
if ([json isKindOfClass:[NSNumber class]]) { \
if ([[mapping allValues] containsObject:json] || [json getter] == default) { \
return [json getter]; \
} \
RCTLogError(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allValues]); \
return default; \
} \
if (![json isKindOfClass:[NSString class]]) { \
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", \
#type, [json classForCoder], json); \
} \
id value = mapping[json]; \
if(!value && [json description].length > 0) { \
RCTLogError(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allKeys]); \
} \
return value ? [value getter] : default; \
NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
}
/**

View File

@@ -115,6 +115,31 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0
// JS standard for time zones is minutes.
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
{
if (!json || json == (id)kCFNull) {
return defaultValue;
}
if ([json isKindOfClass:[NSNumber class]]) {
NSArray *allValues = [mapping allValues];
if ([[mapping allValues] containsObject:json] || [json isEqual:defaultValue]) {
return json;
}
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues);
return defaultValue;
}
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@",
typeName, [json classForCoder], json);
}
id value = mapping[json];
if (!value && [json description].length > 0) {
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, [mapping allKeys]);
}
return value ?: defaultValue;
}
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
@"auto": @(NSTextAlignmentNatural),
@"left": @(NSTextAlignmentLeft),

View File

@@ -14,15 +14,15 @@
#import "RCTSourceCode.h"
#import "RCTWebViewExecutor.h"
@interface RCTDevMenu () <UIActionSheetDelegate> {
BOOL _liveReload;
}
@property (nonatomic, weak) RCTBridge *bridge;
@interface RCTDevMenu () <UIActionSheetDelegate>
@end
@implementation RCTDevMenu
{
BOOL _liveReload;
__weak RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
@@ -34,8 +34,8 @@
- (void)show
{
NSString *debugTitleChrome = self.bridge.executorClass != Nil && self.bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = self.bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
@@ -49,15 +49,15 @@
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
[self.bridge reload];
[_bridge reload];
} else if (buttonIndex == 1) {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : nil;
[self.bridge reload];
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
} else if (buttonIndex == 2) {
Class cls = [RCTWebViewExecutor class];
self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : Nil;
[self.bridge reload];
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
} else if (buttonIndex == 3) {
_liveReload = !_liveReload;
[self _pollAndReload];
@@ -67,7 +67,7 @@
- (void)_pollAndReload
{
if (_liveReload) {
RCTSourceCode *sourceCodeModule = self.bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
NSURL *url = sourceCodeModule.scriptURL;
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
@@ -84,7 +84,7 @@
dispatch_async(dispatch_get_main_queue(), ^{
if (_liveReload && response.statusCode == 205) {
[[RCTRedBox sharedInstance] dismiss];
[self.bridge reload];
[_bridge reload];
}
[self _pollAndReload];
});

View File

@@ -9,8 +9,6 @@
/**
* 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

View File

@@ -24,7 +24,7 @@
*/
@implementation RCTJavaScriptLoader
{
RCTBridge *_bridge;
__weak RCTBridge *_bridge;
}
/**
@@ -46,8 +46,7 @@
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
if (self = [super init]) {
if ((self = [super init])) {
_bridge = bridge;
}
return self;
@@ -56,12 +55,14 @@
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
{
if (scriptURL == nil) {
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader"
code:1
userInfo:@{NSLocalizedDescriptionKey: @"No script URL provided"}];
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
NSLocalizedDescriptionKey: @"No script URL provided"
}];
onComplete(error);
return;
} else if ([scriptURL isFileURL]) {
}
if ([scriptURL isFileURL]) {
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
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.
// 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) {
block(key);
}
@@ -92,10 +93,12 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
modifierFlags:flags
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();

View File

@@ -23,12 +23,6 @@ extern "C" {
#define RCTLOG_FATAL_LEVEL RCTLogLevelMustFix
#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.
*/
@@ -104,24 +98,10 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
*/
void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
#define _RCTLog(lvl, ...) do { \
NSString *msg = [NSString stringWithFormat:__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); \
}\
if (lvl >= RCTLOG_FATAL_LEVEL) { RCTAssert(NO, __VA_ARGS__); } \
_RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \
} 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
* own code.

View File

@@ -55,6 +55,7 @@ RCTLogFunction RCTDefaultLogFunction = ^(
[NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
);
fprintf(stderr, "%s\n", log.UTF8String);
fflush(stderr);
};
void RCTSetLogFunction(RCTLogFunction logFunction)
@@ -148,14 +149,25 @@ NSString *RCTFormatLog(
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
va_list args;
va_start(args, format);
__block NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
// Add prefix
@@ -185,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 "RCTBridge.h"
#import "RCTUtils.h"
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
@@ -120,7 +121,7 @@
- (void)reload
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil userInfo:nil];
[self dismiss];
}

View File

@@ -11,10 +11,6 @@
#import "RCTBridge.h"
extern NSString *const RCTJavaScriptDidLoadNotification;
extern NSString *const RCTReloadNotification;
extern NSString *const RCTReloadViewsNotification;
@interface RCTRootView : UIView <RCTInvalidating>
/**
@@ -68,16 +64,13 @@ extern NSString *const RCTReloadViewsNotification;
@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;
/**
* The React-managed contents view of the root view.
*/
@property (nonatomic, strong, readonly) UIView *contentView;
- (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming;
@end

View File

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

View File

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

View File

@@ -17,9 +17,45 @@
#import "RCTLog.h"
#import "RCTUtils.h"
@interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@property (nonatomic, assign, readonly) JSGlobalContextRef ctx;
- (instancetype)initWithJSContext:(JSGlobalContextRef)context;
@end
@implementation RCTJavaScriptContext
{
RCTJavaScriptContext *_self;
}
- (instancetype)initWithJSContext:(JSGlobalContextRef)context
{
if ((self = [super init])) {
_ctx = context;
_self = self;
}
return self;
}
- (BOOL)isValid
{
return _ctx != NULL;
}
- (void)invalidate
{
JSGlobalContextRelease(_ctx);
_ctx = NULL;
_self = nil;
}
@end
@implementation RCTContextExecutor
{
JSGlobalContextRef _context;
RCTJavaScriptContext *_context;
NSThread *_javaScriptThread;
}
@@ -49,7 +85,7 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object,
range:(NSRange){0, message.length}
withTemplate:@"[$4$5] \t$2"];
_RCTLogFormat(0, NULL, -1, @"%@", message);
_RCTLogFormat(RCTLogLevelInfo, NULL, -1, @"%@", message);
}
return JSValueMakeUndefined(context);
@@ -129,21 +165,28 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
{
if ((self = [super init])) {
_javaScriptThread = javaScriptThread;
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue: ^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Assumes that no other JS tasks are scheduled before.
JSGlobalContextRef ctx;
if (context) {
_context = JSGlobalContextRetain(context);
ctx = JSGlobalContextRetain(context);
} else {
JSContextGroupRef group = JSContextGroupCreate();
_context = JSGlobalContextCreateInGroup(group, NULL);
ctx = JSGlobalContextCreateInGroup(group, NULL);
#if FB_JSC_HACK
JSContextGroupBindToCurrentThread(group);
#endif
JSContextGroupRelease(group);
}
[self _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[self _addNativeHook:RCTNoop withName:"noop"];
strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
[strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[strongSelf _addNativeHook:RCTNoop withName:"noop"];
}];
}
@@ -152,27 +195,24 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name
{
JSObjectRef globalObject = JSContextGetGlobalObject(_context);
JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);
JSStringRef JSName = JSStringCreateWithUTF8CString(name);
JSObjectSetProperty(_context, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context, JSName, hook), kJSPropertyAttributeNone, NULL);
JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL);
JSStringRelease(JSName);
}
- (BOOL)isValid
{
return _context != NULL;
return _context.isValid;
}
- (void)invalidate
{
if ([NSThread currentThread] != _javaScriptThread) {
// Yes, block until done. If we're getting called right before dealloc, it's the only safe option.
[self performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:YES];
} else if (_context != NULL) {
JSGlobalContextRelease(_context);
_context = NULL;
if (self.isValid) {
[_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO];
_context = nil;
}
}
@@ -187,7 +227,12 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.isValid) {
return;
}
NSError *error;
NSString *argsString = RCTJSONStringify(arguments, &error);
if (!argsString) {
@@ -199,11 +244,11 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)execString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, NULL, 0, &jsError);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, NULL, 0, &jsError);
JSStringRelease(execJSString);
if (!result) {
onComplete(nil, RCTNSErrorFromJSError(_context, jsError));
onComplete(nil, RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError));
return;
}
@@ -213,8 +258,8 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
id objcValue;
// We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
// to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
if (!JSValueIsNull(_context, result)) {
JSStringRef jsJSONString = JSValueCreateJSONString(_context, result, 0, nil);
if (!JSValueIsNull(strongSelf->_context.ctx, result)) {
JSStringRef jsJSONString = JSValueCreateJSONString(strongSelf->_context.ctx, result, 0, nil);
if (jsJSONString) {
NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString);
JSStringRelease(jsJSONString);
@@ -233,17 +278,22 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
{
RCTAssert(url != nil, @"url should not be nil");
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.isValid) {
return;
}
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, sourceURL, 0, &jsError);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, sourceURL, 0, &jsError);
JSStringRelease(sourceURL);
JSStringRelease(execJSString);
NSError *error;
if (!result) {
error = RCTNSErrorFromJSError(_context, jsError);
error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError);
}
onComplete(error);
@@ -269,9 +319,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
#endif
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.isValid) {
return;
}
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSValueRef valueToInject = JSValueMakeFromJSONString(_context, execJSString);
JSValueRef valueToInject = JSValueMakeFromJSONString(strongSelf->_context.ctx, execJSString);
JSStringRelease(execJSString);
if (!valueToInject) {
@@ -283,10 +338,10 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
return;
}
JSObjectRef globalObject = JSContextGetGlobalObject(_context);
JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx);
JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName);
JSObjectSetProperty(_context, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
JSStringRelease(JSName);
onComplete(nil);
}];

View File

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

View File

@@ -23,6 +23,7 @@
#import "RCTScrollableProtocol.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTTouchHandler.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTViewManager.h"
@@ -177,7 +178,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
@implementation RCTUIManager
{
dispatch_queue_t _shadowQueue;
__weak dispatch_queue_t _shadowQueue;
// Root views are only mutated on the shadow queue
NSMutableSet *_rootViewTags;
@@ -211,7 +212,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
static NSString *RCTViewNameForModuleName(NSString *moduleName)
{
NSString *name = moduleName;
RCTCAssert(name.length, @"Invalid moduleName '%@'", moduleName);
RCTAssert(name.length, @"Invalid moduleName '%@'", moduleName);
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
@@ -258,31 +259,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
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
{
return _viewRegistry != nil;
@@ -292,8 +268,13 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
{
RCTAssertMainThread();
_viewRegistry = nil;
for (NSNumber *rootViewTag in _rootViewTags) {
((UIView *)_viewRegistry[rootViewTag]).userInteractionEnabled = NO;
}
_rootViewTags = nil;
_shadowViewRegistry = nil;
_viewRegistry = nil;
_bridge = nil;
[_pendingUIBlocksLock lock];
@@ -301,6 +282,25 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
[_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;
{
RCTAssertMainThread();
@@ -310,8 +310,8 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
@"View %@ with tag #%@ is not a root view", rootView, reactTag);
UIView *existingView = _viewRegistry[reactTag];
RCTCAssert(existingView == nil || existingView == rootView,
@"Expect all root views to have unique tag. Added %@ twice", reactTag);
RCTAssert(existingView == nil || existingView == rootView,
@"Expect all root views to have unique tag. Added %@ twice", reactTag);
// Register view
_viewRegistry[reactTag] = rootView;
@@ -322,7 +322,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
// Register shadow view
dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
shadowView.frame = frame;
@@ -549,7 +548,7 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(NSNumber *)containerID)
}
// Construction of removed children must be done "up front", before indices are disturbed by removals.
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++) {
NSInteger index = [atIndices[i] integerValue];
if (index < [[container reactSubviews] count]) {
@@ -578,7 +577,7 @@ RCT_EXPORT_METHOD(removeRootView:(NSNumber *)rootReactTag)
[_rootViewTags removeObject:rootReactTag];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTCAssertMainThread();
RCTAssertMainThread();
UIView *rootView = viewRegistry[rootReactTag];
[uiManager _purgeChildren:rootView.reactSubviews fromRegistry:viewRegistry];
viewRegistry[rootReactTag] = nil;
@@ -667,7 +666,7 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
NSArray *sortedIndices = [[destinationsToChildrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)];
for (NSNumber *reactIndex in sortedIndices) {
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:[reactIndex integerValue]];
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
}
}
@@ -758,7 +757,7 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
_shadowViewRegistry[reactTag] = shadowView;
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTCAssertMainThread();
RCTAssertMainThread();
UIView *view = [manager view];
if (view) {
@@ -783,6 +782,7 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
viewRegistry[reactTag] = view;
}];
}
// TODO: remove viewName param as it isn't needed
RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag
viewName:(__unused NSString *)_
@@ -899,7 +899,7 @@ RCT_EXPORT_METHOD(measure:(NSNumber *)reactTag
}
// 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
// measurement coordinates are with respect to the root view.
@@ -921,11 +921,9 @@ static void RCTMeasureLayout(RCTShadowView *view,
RCTResponseSenderBlock callback)
{
if (!view) {
RCTLogError(@"Attempting to measure view that does not exist");
return;
}
if (!ancestor) {
RCTLogError(@"Attempting to measure relative to ancestor that does not exist");
return;
}
CGRect result = [view measureLayoutRelativeToAncestor:ancestor];
@@ -1039,12 +1037,12 @@ RCT_EXPORT_METHOD(setMainScrollViewTag:(NSNumber *)reactTag)
uiManager.mainScrollView.nativeMainScrollDelegate = nil;
}
if (reactTag) {
id rkObject = viewRegistry[reactTag];
if ([rkObject conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
uiManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject;
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
id view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
uiManager.mainScrollView = (id<RCTScrollableProtocol>)view;
uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
} else {
RCTCAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
RCTAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
}
} else {
uiManager.mainScrollView = nil;
@@ -1052,28 +1050,30 @@ RCT_EXPORT_METHOD(setMainScrollViewTag:(NSNumber *)reactTag)
}];
}
// TODO: we could just pass point property
RCT_EXPORT_METHOD(scrollTo:(NSNumber *)reactTag
withOffsetX:(NSNumber *)offsetX
offsetY:(NSNumber *)offsetY)
withOffsetX:(CGFloat)offsetX
offsetY:(CGFloat)offsetY)
{
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
UIView *view = viewRegistry[reactTag];
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 {
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
offsetX:(NSNumber *)offsetX
offsetY:(NSNumber *)offsetY)
offsetX:(CGFloat)offsetX
offsetY:(CGFloat)offsetY)
{
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
UIView *view = viewRegistry[reactTag];
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 {
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
}
@@ -1081,12 +1081,12 @@ RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag
}
RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
withRect:(NSDictionary *)rectDict)
withRect:(CGRect)rect)
{
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES];
[(id<RCTScrollableProtocol>)view zoomToRect:rect animated:YES];
} else {
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
}
@@ -1212,8 +1212,8 @@ RCT_EXPORT_METHOD(clearJSResponder)
if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) {
NSDictionary *eventTypes = [manager customBubblingEventTypes];
for (NSString *eventName in eventTypes) {
RCTCAssert(!customBubblingEventTypesConfigs[eventName],
@"Event '%@' registered multiple times.", eventName);
RCTAssert(!customBubblingEventTypesConfigs[eventName],
@"Event '%@' registered multiple times.", eventName);
}
[customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
}
@@ -1264,7 +1264,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
NSDictionary *eventTypes = [manager customDirectEventTypes];
for (NSString *eventName in eventTypes) {
RCTCAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
RCTAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
}
[customDirectEventTypes addEntriesFromDictionary:eventTypes];
}
@@ -1398,9 +1398,12 @@ RCT_EXPORT_METHOD(startOrResetInteractionTiming)
NSSet *rootViewTags = [_rootViewTags copy];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (NSNumber *reactTag in rootViewTags) {
id rootView = viewRegistry[reactTag];
if ([rootView respondsToSelector:@selector(startOrResetInteractionTiming)]) {
[rootView startOrResetInteractionTiming];
UIView *rootView = viewRegistry[reactTag];
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
[handler startOrResetInteractionTiming];
break;
}
}
}
}];
@@ -1413,9 +1416,12 @@ RCT_EXPORT_METHOD(endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init];
for (NSNumber *reactTag in rootViewTags) {
id rootView = viewRegistry[reactTag];
if ([rootView respondsToSelector:@selector(endAndResetInteractionTiming)]) {
timingData[reactTag.stringValue] = [rootView endAndResetInteractionTiming];
UIView *rootView = viewRegistry[reactTag];
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
[handler endAndResetInteractionTiming];
break;
}
}
}
onSuccess(@[timingData]);

View File

@@ -28,7 +28,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* allowing the manager (or the views that it manages) to manipulate the view
* hierarchy and send events back to the JS context.
*/
@property (nonatomic, strong) RCTBridge *bridge;
@property (nonatomic, weak) RCTBridge *bridge;
/**
* This method instantiates a native view to be managed by the module. Override