From 26fd24dc501ee0027a966b4e5fd836137ff42fcb Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sat, 11 Apr 2015 15:08:00 -0700 Subject: [PATCH] Cleanup --- Libraries/RCTTest/RCTTestRunner.h | 4 +- Libraries/RCTTest/RCTTestRunner.m | 17 +- .../RCTWebSocketExecutor.h | 2 +- .../RCTWebSocketExecutor.m | 68 ++-- React/Base/RCTAssert.h | 86 +++-- React/Base/RCTAssert.m | 53 ++- React/Base/RCTBridge.h | 18 +- React/Base/RCTBridge.m | 337 +++++++++--------- React/Base/RCTBridgeModule.h | 18 +- React/Base/RCTDevMenu.m | 28 +- React/Base/RCTJavaScriptLoader.h | 2 - React/Base/RCTJavaScriptLoader.m | 13 +- React/Base/RCTKeyCommands.m | 9 +- React/Base/RCTLog.h | 22 +- React/Base/RCTLog.m | 40 +-- React/Base/RCTRedBox.m | 3 +- React/Base/RCTRootView.h | 15 +- React/Base/RCTRootView.m | 116 ++---- React/Base/RCTTouchHandler.m | 10 +- React/Executors/RCTWebViewExecutor.m | 7 +- React/Modules/RCTUIManager.m | 121 ++++--- 21 files changed, 504 insertions(+), 485 deletions(-) diff --git a/Libraries/RCTTest/RCTTestRunner.h b/Libraries/RCTTest/RCTTestRunner.h index 9d56202ce..6dc1ddb06 100644 --- a/Libraries/RCTTest/RCTTestRunner.h +++ b/Libraries/RCTTest/RCTTestRunner.h @@ -21,7 +21,7 @@ @interface RCTTestRunner : NSObject @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. @@ -55,7 +55,7 @@ * @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. */ -- (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 diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 8cb5169c3..12eaf8072 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -29,7 +29,7 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _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; } @@ -49,10 +49,10 @@ [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){ - 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]; testModule.testSelector = test; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:_script - moduleProvider:^(){ - return @[testModule]; - } - launchOptions:nil]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL + moduleProvider:^(){ + return @[testModule]; + } + launchOptions:nil]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName]; testModule.view = rootView; diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h index 52c1d4f15..3fc062a37 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h @@ -11,6 +11,6 @@ @interface RCTWebSocketExecutor : NSObject -- (instancetype)initWithURL:(NSURL *)url; +- (instancetype)initWithURL:(NSURL *)URL; @end diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index 2f74628c0..784c91e12 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -10,6 +10,7 @@ #import "RCTWebSocketExecutor.h" #import "RCTLog.h" +#import "RCTSparseArray.h" #import "RCTUtils.h" #import "SRWebSocket.h" @@ -18,10 +19,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); @interface RCTWebSocketExecutor () @end -@implementation RCTWebSocketExecutor { +@implementation RCTWebSocketExecutor +{ SRWebSocket *_socket; - NSOperationQueue *_jsQueue; - NSMutableDictionary *_callbacks; + dispatch_queue_t _jsQueue; + RCTSparseArray *_callbacks; dispatch_semaphore_t _socketOpenSemaphore; NSMutableDictionary *_injectedObjects; } @@ -31,23 +33,24 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]]; } -- (instancetype)initWithURL:(NSURL *)url +- (instancetype)initWithURL:(NSURL *)URL { if (self = [super init]) { - _jsQueue = [[NSOperationQueue alloc] init]; - _jsQueue.maxConcurrentOperationCount = 1; - _socket = [[SRWebSocket alloc] initWithURL:url]; + + _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); + _socket = [[SRWebSocket alloc] initWithURL:URL]; _socket.delegate = self; - _callbacks = [NSMutableDictionary dictionary]; - _injectedObjects = [NSMutableDictionary dictionary]; - [_socket setDelegateOperationQueue:_jsQueue]; + _callbacks = [[RCTSparseArray alloc] init]; + _injectedObjects = [[NSMutableDictionary alloc] init]; + [_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]; 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]; return nil; } @@ -91,8 +94,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); { NSError *error = nil; NSDictionary *reply = RCTJSONParse(message, &error); - NSUInteger messageID = [reply[@"replyID"] integerValue]; - WSMessageCallback callback = [_callbacks objectForKey:@(messageID)]; + NSNumber *messageID = reply[@"replyID"]; + WSMessageCallback callback = _callbacks[messageID]; if (callback) { callback(error, reply); } @@ -108,16 +111,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); 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 { static NSUInteger lastID = 10000; - [_jsQueue addOperationWithBlock:^{ + dispatch_async(_jsQueue, ^{ if (!self.valid) { NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{ NSLocalizedDescriptionKey: @"socket closed" @@ -126,19 +124,17 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); return; } - NSUInteger expectedID = lastID++; - - _callbacks[@(expectedID)] = [callback copy]; - + NSNumber *expectedID = @(lastID++); + _callbacks[expectedID] = [callback copy]; NSMutableDictionary *messageWithID = [message mutableCopy]; - messageWithID[@"id"] = @(expectedID); + messageWithID[@"id"] = expectedID; [_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) { 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 { 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) { if (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 { - [_jsQueue addOperationWithBlock:^{ - [_injectedObjects setObject:script forKey:objectName]; + dispatch_async(_jsQueue, ^{ + _injectedObjects[objectName] = script; onComplete(nil); - }]; + }); } - (void)invalidate { - [_jsQueue cancelAllOperations]; _socket.delegate = nil; [_socket closeWithCode:1000 reason:@"Invalidated"]; _socket = nil; diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 2c5d82b73..7e73aed7d 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -9,35 +9,73 @@ #import -#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 diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index 378cb244b..86d71cd80 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -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; + } } diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index f5c21bb3c..2ff4d9c1e 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -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. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 3e6d749e9..0925633bd 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -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)]; @@ -279,34 +282,34 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) { // 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( - if (json && ![json isKindOfClass:[NSNumber class]]) { - RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, - json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); - return; - } + if (json && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, + json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); + return; + } - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing id value = (json ? ^(NSArray *args) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, args]]; - } : ^(NSArray *unused) {}); - ) + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing id value = (json ? ^(NSArray *args) { + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, args]]; + } : ^(NSArray *unused) {}); + ) }; void (^defaultCase)(const char *) = ^(const char *argumentType) { @@ -330,29 +333,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 +371,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); @@ -500,13 +503,13 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) // Create method RCTModuleMethod *moduleMethod = - [[RCTModuleMethod alloc] initWithMethodName:@(entries[0]) - JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil]; + [[RCTModuleMethod alloc] initWithMethodName:@(entries[0]) + JSMethodName:strlen(entries[1]) ? @(entries[1]) : 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]]; @@ -557,15 +560,15 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count]; [methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) { methodsByName[method.JSMethodName] = @{ - @"methodID": @(methodID), - @"type": @"remote", - }; + @"methodID": @(methodID), + @"type": @"remote", + }; }]; NSDictionary *module = @{ - @"moduleID": @(moduleID), - @"methods": methodsByName - }; + @"moduleID": @(moduleID), + @"methods": methodsByName + }; remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module; }]; @@ -629,16 +632,16 @@ 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]; NSDictionary *module = localModules[moduleName]; if (!module) { module = @{ - @"moduleID": @(localModules.count), - @"methods": [[NSMutableDictionary alloc] init] - }; + @"moduleID": @(localModules.count), + @"methods": [[NSMutableDictionary alloc] init] + }; localModules[moduleName] = module; } @@ -647,9 +650,9 @@ static NSDictionary *RCTLocalModulesConfig() NSMutableDictionary *methods = module[@"methods"]; if (!methods[methodName]) { methods[methodName] = @{ - @"methodID": @(methods.count), - @"type": @"local" - }; + @"methodID": @(methods.count), + @"type": @"local" + }; } // Add module and method lookup @@ -667,22 +670,21 @@ static NSDictionary *RCTLocalModulesConfig() NSDictionary *_modulesByName; id _javaScriptExecutor; Class _executorClass; - NSString *_bundlePath; - NSDictionary *_launchOptions; + NSURL *_bundleURL; RCTBridgeModuleProviderBlock _moduleProvider; - BOOL _loaded; + BOOL _loading; } static id _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 +697,7 @@ static id _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]; @@ -743,21 +745,25 @@ static id _latestJSExecutor; @"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) { @@ -770,6 +776,11 @@ static id _latestJSExecutor; } else { [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reload) + name:RCTReloadNotification + object:nil]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) @@ -781,53 +792,56 @@ static id _latestJSExecutor; - (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; } @@ -874,7 +888,6 @@ static id _latestJSExecutor; // Release modules (breaks retain cycle if module has strong bridge reference) _modulesByID = nil; _modulesByName = nil; - _loaded = NO; } /** @@ -898,10 +911,10 @@ static id _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 ?: @[]]]; } } @@ -1051,20 +1064,18 @@ static id _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; } diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index e9a5ec41c..d7322ddbf 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -38,8 +38,8 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); * 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; } \ + + (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 @@ -59,17 +59,17 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); * 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). * - * 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`. */ @@ -96,11 +96,3 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); - (void)batchDidComplete; @end - -#ifdef __cplusplus -extern "C" { -#endif -void RCTBridgeModuleRegisterClass(Class cls, NSString *moduleName); -#ifdef __cplusplus -} -#endif diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index fdb79b8fb..a0ebc25c1 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -14,15 +14,15 @@ #import "RCTSourceCode.h" #import "RCTWebViewExecutor.h" -@interface RCTDevMenu () { - BOOL _liveReload; -} - -@property (nonatomic, weak) RCTBridge *bridge; +@interface RCTDevMenu () @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]; }); diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index 7c750c585..bdc551b4d 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -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 diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index ff5371bb4..baf2ca344 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -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]; diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index 7f877fb5b..9141dd31d 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -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(); diff --git a/React/Base/RCTLog.h b/React/Base/RCTLog.h index bc19448ac..7ffd86006 100644 --- a/React/Base/RCTLog.h +++ b/React/Base/RCTLog.h @@ -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. diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 02f12f467..1770a20a2 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -149,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 @@ -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); - } - }); -} diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index f5e8fbbb4..3bed31505 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -9,6 +9,7 @@ #import "RCTRedBox.h" +#import "RCTBridge.h" #import "RCTUtils.h" @interface RCTRedBoxWindow : UIWindow @@ -120,7 +121,7 @@ - (void)reload { - [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil userInfo:nil]; [self dismiss]; } diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index f85bb7ecb..1227eba94 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -11,10 +11,6 @@ #import "RCTBridge.h" -extern NSString *const RCTJavaScriptDidLoadNotification; -extern NSString *const RCTReloadNotification; -extern NSString *const RCTReloadViewsNotification; - @interface RCTRootView : UIView /** @@ -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 diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 80df8a44d..0f2332e63 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -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,57 +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) { - [[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]]; - } + 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 { @@ -143,7 +123,7 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; { if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) { if (!_devMenu) { - _devMenu = [[RCTDevMenu alloc] initWithBridge:self.bridge]; + _devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge]; } [_devMenu show]; } @@ -155,7 +135,7 @@ 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 @@ -163,6 +143,8 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) * 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]; @@ -183,45 +165,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) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index d409ec3e5..910ae950e 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -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]), }]; } } diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index e50fff904..55de44ab9 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -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 diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 8ce1b0984..86c58d0dd 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -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" @@ -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; @@ -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. 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]) { @@ -577,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; @@ -666,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]; } } @@ -757,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) { @@ -782,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 *)_ @@ -898,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. @@ -1036,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)rkObject; - ((id)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate; + id view = viewRegistry[reactTag]; + if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { + uiManager.mainScrollView = (id)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; @@ -1049,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)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:YES]; + [(id)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)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:NO]; + [(id)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:NO]; } else { 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 - withRect:(NSDictionary *)rectDict) + withRect:(CGRect)rect) { [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ UIView *view = viewRegistry[reactTag]; if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES]; + [(id)view zoomToRect:rect animated:YES]; } else { 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))) { 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]; } @@ -1261,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]; } @@ -1395,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; + } } } }]; @@ -1410,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]);