mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
Updates from Thu 26 Mar
- [React Native] Fix incorrect if-statement in RCTGeolocation | Alex Akers - [ReactNative] s/ReactKit/React/g | Tadeu Zagallo - [React Native] View border support | Nick Lockwood - [Assets] Allow scripts to override assetRoots | Amjad Masad - [ReactNative] Navigator docs | Eric Vicenti - [ReactNative] License headers and renaming | Eric Vicenti - [React Native] Add CocoaPods spec | Tadeu Zagallo - Added explicit types for all view properties | Nick Lockwood - [ReactNative] s/ReactNavigator/Navigator/ | Tadeu Zagallo - [ReactNative] Add copyright header for code copied from the jQuery UI project | Martin Konicek - [ReactNative] PanResponder documentation | Eric Vicenti
This commit is contained in:
43
React/Base/RCTAssert.h
Normal file
43
React/Base/RCTAssert.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define RCTErrorDomain @"RCTErrorDomain"
|
||||
|
||||
#define RCTAssert(condition, message, ...) _RCTAssert((condition) != 0, message, ##__VA_ARGS__)
|
||||
#define RCTCAssert(condition, message, ...) _RCTCAssert((condition) != 0, message, ##__VA_ARGS__)
|
||||
|
||||
typedef void (^RCTAssertFunction)(BOOL condition, NSString *message, ...);
|
||||
|
||||
extern RCTAssertFunction RCTInjectedAssertFunction;
|
||||
extern RCTAssertFunction RCTInjectedCAssertFunction;
|
||||
|
||||
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction);
|
||||
|
||||
#define _RCTAssert(condition, message, ...) \
|
||||
do { \
|
||||
if (RCTInjectedAssertFunction) { \
|
||||
RCTInjectedAssertFunction(condition, message, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
NSAssert(condition, message, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define _RCTCAssert(condition, message, ...) \
|
||||
do { \
|
||||
if (RCTInjectedCAssertFunction) { \
|
||||
RCTInjectedCAssertFunction(condition, message, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
NSCAssert(condition, message, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define RCTAssertMainThread() RCTAssert([NSThread isMainThread], @"This method must be called on the main thread");
|
||||
#define RCTCAssertMainThread() RCTCAssert([NSThread isMainThread], @"This function must be called on the main thread");
|
||||
19
React/Base/RCTAssert.m
Normal file
19
React/Base/RCTAssert.m
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
RCTAssertFunction RCTInjectedAssertFunction = nil;
|
||||
RCTAssertFunction RCTInjectedCAssertFunction = nil;
|
||||
|
||||
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction)
|
||||
{
|
||||
RCTInjectedAssertFunction = assertFunction;
|
||||
RCTInjectedCAssertFunction = cAssertFunction;
|
||||
}
|
||||
92
React/Base/RCTBridge.h
Normal file
92
React/Base/RCTBridge.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@class RCTBridge;
|
||||
@class RCTEventDispatcher;
|
||||
|
||||
/**
|
||||
* This block can be used to instantiate modules that require additional
|
||||
* init parameters, or additional configuration prior to being used.
|
||||
* The bridge will call this block to instatiate the modules, and will
|
||||
* be responsible for invalidating/releasing them when the bridge is destroyed.
|
||||
* For this reason, the block should always return new module instances, and
|
||||
* module instances should not be shared between bridges.
|
||||
*/
|
||||
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
|
||||
|
||||
/**
|
||||
* Async batched bridge used to communicate with the JavaScript application.
|
||||
*/
|
||||
@interface RCTBridge : NSObject <RCTInvalidating>
|
||||
|
||||
/**
|
||||
* The designated initializer. This creates a new bridge on top of the specified
|
||||
* executor. The bridge should then be used for all subsequent communication
|
||||
* with the JavaScript code running in the executor. Modules will be automatically
|
||||
* instantiated using the default contructor, but you can optionally pass in an
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This method is used to call functions in the JavaScript application context.
|
||||
* It is primarily intended for use by modules that require two-way communication
|
||||
* with the JavaScript code.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
|
||||
|
||||
/**
|
||||
* This method is used to execute a new application script. It is called
|
||||
* internally whenever a JS application bundle is loaded/reloaded, but should
|
||||
* probably not be used at any other time.
|
||||
*/
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
/**
|
||||
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
|
||||
* higher-level interface for sending UI events such as touches and text input.
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
|
||||
|
||||
/**
|
||||
* A dictionary of all registered RCTBridgeModule instances, keyed by moduleName.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *modules;
|
||||
|
||||
/**
|
||||
* The shadow queue is used to execute callbacks from the JavaScript code. All
|
||||
* native hooks (e.g. exported module methods) will be executed on the shadow
|
||||
* queue.
|
||||
*/
|
||||
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
|
||||
|
||||
/**
|
||||
* Global logging function that will print to both xcode and JS debugger consoles.
|
||||
*
|
||||
* NOTE: Use via RCTLog* macros defined in RCTLog.h
|
||||
* TODO (#5906496): should log function be exposed here, or could it be a module?
|
||||
*/
|
||||
+ (void)log:(NSArray *)objects level:(NSString *)level;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary *launchOptions;
|
||||
|
||||
|
||||
/**
|
||||
* Method to check that a valid executor exists with which to log
|
||||
*/
|
||||
+ (BOOL)hasValidJSExecutor;
|
||||
|
||||
@end
|
||||
826
React/Base/RCTBridge.m
Normal file
826
React/Base/RCTBridge.m
Normal file
@@ -0,0 +1,826 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
#import <dlfcn.h>
|
||||
#import <objc/message.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <mach-o/dyld.h>
|
||||
#import <mach-o/getsect.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
/**
|
||||
* Must be kept in sync with `MessageQueue.js`.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
||||
RCTBridgeFieldRequestModuleIDs = 0,
|
||||
RCTBridgeFieldMethodIDs,
|
||||
RCTBridgeFieldParamss,
|
||||
RCTBridgeFieldResponseCBIDs,
|
||||
RCTBridgeFieldResponseReturnValues,
|
||||
RCTBridgeFieldFlushDateMillis
|
||||
};
|
||||
|
||||
/**
|
||||
* This function returns the module name for a given class.
|
||||
*/
|
||||
static NSString *RCTModuleNameForClass(Class cls)
|
||||
{
|
||||
return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function scans all classes available at runtime and returns an array
|
||||
* of all JSMethods registered.
|
||||
*/
|
||||
static NSArray *RCTJSMethods(void)
|
||||
{
|
||||
static NSArray *JSMethods;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableSet *uniqueMethods = [NSMutableSet set];
|
||||
|
||||
RCTEnumerateClasses(^(__unsafe_unretained Class cls) {
|
||||
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
|
||||
[uniqueMethods addObjectsFromArray:[cls JSMethods]];
|
||||
}
|
||||
});
|
||||
|
||||
JSMethods = [uniqueMethods allObjects];
|
||||
});
|
||||
|
||||
return JSMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function scans all classes available at runtime and returns an array
|
||||
* of all classes that implement the RTCBridgeModule protocol.
|
||||
*/
|
||||
static NSArray *RCTModuleNamesByID;
|
||||
static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
{
|
||||
static NSArray *modules;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
modules = [NSMutableArray array];
|
||||
RCTModuleNamesByID = [NSMutableArray array];
|
||||
|
||||
RCTEnumerateClasses(^(__unsafe_unretained Class cls) {
|
||||
if ([cls conformsToProtocol:@protocol(RCTBridgeModule)]) {
|
||||
|
||||
// Add module
|
||||
[(NSMutableArray *)modules addObject:cls];
|
||||
|
||||
// Add module name
|
||||
NSString *moduleName = RCTModuleNameForClass(cls);
|
||||
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
|
||||
}
|
||||
});
|
||||
|
||||
modules = [modules copy];
|
||||
RCTModuleNamesByID = [RCTModuleNamesByID copy];
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
@interface RCTBridge ()
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This private class is used as a container for exported method info
|
||||
*/
|
||||
@interface RCTModuleMethod : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *moduleClassName;
|
||||
@property (nonatomic, copy, readonly) NSString *JSMethodName;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTModuleMethod
|
||||
{
|
||||
BOOL _isClassMethod;
|
||||
Class _moduleClass;
|
||||
SEL _selector;
|
||||
NSMethodSignature *_methodSignature;
|
||||
NSArray *_argumentBlocks;
|
||||
NSString *_methodName;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMethodName:(NSString *)methodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_methodName = methodName;
|
||||
NSArray *parts = [[methodName substringWithRange:NSMakeRange(2, methodName.length - 3)] componentsSeparatedByString:@" "];
|
||||
|
||||
// Parse class and method
|
||||
_moduleClassName = parts[0];
|
||||
NSRange categoryRange = [_moduleClassName rangeOfString:@"("];
|
||||
if (categoryRange.length)
|
||||
{
|
||||
_moduleClassName = [_moduleClassName substringToIndex:categoryRange.location];
|
||||
}
|
||||
|
||||
// Extract class and method details
|
||||
_isClassMethod = [methodName characterAtIndex:0] == '+';
|
||||
_moduleClass = NSClassFromString(_moduleClassName);
|
||||
_selector = NSSelectorFromString(parts[1]);
|
||||
_JSMethodName = JSMethodName ?: [NSStringFromSelector(_selector) componentsSeparatedByString:@":"][0];
|
||||
|
||||
#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);
|
||||
#endif
|
||||
|
||||
// Get method signature
|
||||
_methodSignature = _isClassMethod ?
|
||||
[_moduleClass methodSignatureForSelector:_selector] :
|
||||
[_moduleClass instanceMethodSignatureForSelector:_selector];
|
||||
|
||||
// Process arguments
|
||||
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
|
||||
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
|
||||
for (NSUInteger i = 2; i < numberOfArguments; i++) {
|
||||
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
|
||||
switch (argumentType[0]) {
|
||||
|
||||
#define RCT_ARG_BLOCK(_logic) \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
}]; \
|
||||
|
||||
#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, RCTModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
|
||||
return; \
|
||||
} \
|
||||
_logic \
|
||||
) \
|
||||
break; \
|
||||
}
|
||||
|
||||
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, RCTModuleNameForClass(_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)
|
||||
|
||||
default: {
|
||||
static const char *blockType = @encode(typeof(^{}));
|
||||
if (!strcmp(argumentType, blockType)) {
|
||||
RCT_ARG_BLOCK(
|
||||
if (json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTModuleNameForClass(_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) {});
|
||||
)
|
||||
} else {
|
||||
RCT_ARG_BLOCK( id value = json; )
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_argumentBlocks = [argumentBlocks copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)invokeWithBridge:(RCTBridge *)bridge
|
||||
module:(id)module
|
||||
arguments:(NSArray *)arguments
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// Sanity check
|
||||
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
|
||||
%@ on a module of class %@", _methodName, [module class]);
|
||||
#endif
|
||||
|
||||
// Safety check
|
||||
if (arguments.count != _argumentBlocks.count) {
|
||||
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
|
||||
RCTModuleNameForClass(_moduleClass), _JSMethodName,
|
||||
arguments.count, _argumentBlocks.count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature];
|
||||
[invocation setArgument:&_selector atIndex:1];
|
||||
|
||||
// Set arguments
|
||||
NSUInteger index = 0;
|
||||
for (id json in arguments) {
|
||||
id arg = (json == [NSNull null]) ? nil : json;
|
||||
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, invocation, index + 2, arg);
|
||||
index ++;
|
||||
}
|
||||
|
||||
// Invoke method
|
||||
[invocation invokeWithTarget:_isClassMethod ? [module class] : module];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This function parses the exported methods inside RCTBridgeModules and
|
||||
* generates an array of arrays of RCTModuleMethod objects, keyed
|
||||
* by module index.
|
||||
*/
|
||||
static RCTSparseArray *RCTExportedMethodsByModuleID(void)
|
||||
{
|
||||
static RCTSparseArray *methodsByModuleID;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
Dl_info info;
|
||||
dladdr(&RCTExportedMethodsByModuleID, &info);
|
||||
|
||||
#ifdef __LP64__
|
||||
typedef uint64_t RCTExportValue;
|
||||
typedef struct section_64 RCTExportSection;
|
||||
#define RCTGetSectByNameFromHeader getsectbynamefromheader_64
|
||||
#else
|
||||
typedef uint32_t RCTExportValue;
|
||||
typedef struct section RCTExportSection;
|
||||
#define RCTGetSectByNameFromHeader getsectbynamefromheader
|
||||
#endif
|
||||
|
||||
const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase;
|
||||
const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport");
|
||||
|
||||
if (section == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *classes = RCTBridgeModuleClassesByModuleID();
|
||||
NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]];
|
||||
|
||||
for (RCTExportValue addr = section->offset;
|
||||
addr < section->offset + section->size;
|
||||
addr += sizeof(const char **) * 2) {
|
||||
|
||||
// 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];
|
||||
|
||||
// Cache method
|
||||
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
|
||||
methodsByModuleClassName[moduleMethod.moduleClassName] =
|
||||
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
|
||||
}
|
||||
|
||||
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]];
|
||||
[classes enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
|
||||
methodsByModuleID[moduleID] = methodsByModuleClassName[NSStringFromClass(moduleClass)];
|
||||
}];
|
||||
});
|
||||
|
||||
return methodsByModuleID;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs the remote modules configuration data structure,
|
||||
* which represents the native modules and methods that will be called
|
||||
* by JS. A numeric ID is assigned to each module and method, which will
|
||||
* be used to communicate via the bridge. The structure of each
|
||||
* module is as follows:
|
||||
*
|
||||
* "ModuleName1": {
|
||||
* "moduleID": 0,
|
||||
* "methods": {
|
||||
* "methodName1": {
|
||||
* "methodID": 0,
|
||||
* "type": "remote"
|
||||
* },
|
||||
* "methodName2": {
|
||||
* "methodID": 1,
|
||||
* "type": "remote"
|
||||
* },
|
||||
* etc...
|
||||
* },
|
||||
* "constants": {
|
||||
* ...
|
||||
* }
|
||||
* },
|
||||
* etc...
|
||||
*/
|
||||
static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
||||
{
|
||||
static NSMutableDictionary *remoteModuleConfigByClassName;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
remoteModuleConfigByClassName = [[NSMutableDictionary alloc] init];
|
||||
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
|
||||
|
||||
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
|
||||
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
|
||||
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
|
||||
methodsByName[method.JSMethodName] = @{
|
||||
@"methodID": @(methodID),
|
||||
@"type": @"remote",
|
||||
};
|
||||
}];
|
||||
|
||||
NSDictionary *module = @{
|
||||
@"moduleID": @(moduleID),
|
||||
@"methods": methodsByName
|
||||
};
|
||||
|
||||
remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module;
|
||||
}];
|
||||
});
|
||||
|
||||
// Create config
|
||||
NSMutableDictionary *moduleConfig = [[NSMutableDictionary alloc] init];
|
||||
[modulesByName enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, id<RCTBridgeModule> module, BOOL *stop) {
|
||||
|
||||
// Add constants
|
||||
NSMutableDictionary *config = remoteModuleConfigByClassName[NSStringFromClass([module class])];
|
||||
if ([module respondsToSelector:@selector(constantsToExport)]) {
|
||||
NSDictionary *constants = [module constantsToExport];
|
||||
if (constants) {
|
||||
NSMutableDictionary *mutableConfig = [NSMutableDictionary dictionaryWithDictionary:config];
|
||||
mutableConfig[@"constants"] = constants; // There's no real need to copy this
|
||||
config = mutableConfig; // Nor this - receiver is unlikely to mutate it
|
||||
}
|
||||
}
|
||||
|
||||
moduleConfig[moduleName] = config;
|
||||
}];
|
||||
|
||||
return moduleConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* As above, but for local modules/methods, which represent JS classes
|
||||
* and methods that will be called by the native code via the bridge.
|
||||
* Structure is essentially the same as for remote modules:
|
||||
*
|
||||
* "ModuleName1": {
|
||||
* "moduleID": 0,
|
||||
* "methods": {
|
||||
* "methodName1": {
|
||||
* "methodID": 0,
|
||||
* "type": "local"
|
||||
* },
|
||||
* "methodName2": {
|
||||
* "methodID": 1,
|
||||
* "type": "local"
|
||||
* },
|
||||
* etc...
|
||||
* }
|
||||
* },
|
||||
* etc...
|
||||
*/
|
||||
static NSMutableDictionary *RCTLocalModuleIDs;
|
||||
static NSMutableDictionary *RCTLocalMethodIDs;
|
||||
static NSDictionary *RCTLocalModulesConfig()
|
||||
{
|
||||
static NSMutableDictionary *localModules;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
|
||||
|
||||
localModules = [[NSMutableDictionary alloc] init];
|
||||
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);
|
||||
|
||||
// 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]
|
||||
};
|
||||
localModules[moduleName] = module;
|
||||
}
|
||||
|
||||
// Add method if it doesn't already exist
|
||||
NSString *methodName = parts[1];
|
||||
NSMutableDictionary *methods = module[@"methods"];
|
||||
if (!methods[methodName]) {
|
||||
methods[methodName] = @{
|
||||
@"methodID": @(methods.count),
|
||||
@"type": @"local"
|
||||
};
|
||||
}
|
||||
|
||||
// Add module and method lookup
|
||||
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
|
||||
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
|
||||
}
|
||||
});
|
||||
|
||||
return localModules;
|
||||
}
|
||||
|
||||
@implementation RCTBridge
|
||||
{
|
||||
RCTSparseArray *_modulesByID;
|
||||
NSDictionary *_modulesByName;
|
||||
id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
||||
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||
}
|
||||
|
||||
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
- (instancetype)initWithBundlePath:(NSString *)bundlepath
|
||||
moduleProvider:(RCTBridgeModuleProviderBlock)block
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_moduleProvider = block;
|
||||
_launchOptions = launchOptions;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor
|
||||
{
|
||||
_javaScriptExecutor = executor;
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
[self setUp];
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
|
||||
preregisteredModules[RCTModuleNameForClass([module class])] = module;
|
||||
}
|
||||
|
||||
// Instantiate modules
|
||||
_modulesByID = [[RCTSparseArray alloc] init];
|
||||
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
|
||||
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
|
||||
NSString *moduleName = RCTModuleNamesByID[moduleID];
|
||||
// Check if module instance has already been registered for this name
|
||||
if ((_modulesByID[moduleID] = modulesByName[moduleName])) {
|
||||
// Preregistered instances takes precedence, no questions asked
|
||||
if (!preregisteredModules[moduleName]) {
|
||||
// It's OK to have a name collision as long as the second instance is nil
|
||||
RCTAssert([[moduleClass alloc] init] == nil,
|
||||
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \
|
||||
but name was already registered by class %@", moduleClass,
|
||||
moduleName, [modulesByName[moduleName] class]);
|
||||
}
|
||||
} else {
|
||||
// Module name hasn't been used before, so go ahead and instantiate
|
||||
id<RCTBridgeModule> module = [[moduleClass alloc] init];
|
||||
if (module) {
|
||||
_modulesByID[moduleID] = modulesByName[moduleName] = module;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
// Store modules
|
||||
_modulesByName = [modulesByName copy];
|
||||
|
||||
// Set bridge
|
||||
for (id<RCTBridgeModule> module in _modulesByName.allValues) {
|
||||
if ([module respondsToSelector:@selector(setBridge:)]) {
|
||||
module.bridge = self;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject module data into JS context
|
||||
NSString *configJSON = RCTJSONStringify(@{
|
||||
@"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);
|
||||
}];
|
||||
|
||||
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
|
||||
RCTLogError(@"JavaScriptExecutor took too long to inject JSON object");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (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.");
|
||||
|
||||
return _modulesByName;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _javaScriptExecutor != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
// Release executor
|
||||
if (_latestJSExecutor == _javaScriptExecutor) {
|
||||
_latestJSExecutor = nil;
|
||||
}
|
||||
[_javaScriptExecutor invalidate];
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
// Wait for queued methods to finish
|
||||
dispatch_sync(self.shadowQueue, ^{
|
||||
// Make sure all dispatchers have been executed before continuing
|
||||
// TODO: is this still needed?
|
||||
});
|
||||
|
||||
// Invalidate modules
|
||||
for (id target in _modulesByID.allObjects) {
|
||||
if ([target respondsToSelector:@selector(invalidate)]) {
|
||||
[(id<RCTInvalidating>)target invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
// Release modules (breaks retain cycle if module has strong bridge reference)
|
||||
_modulesByID = nil;
|
||||
_modulesByName = nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* - TODO (#5906496): When we build a `MessageQueue.m`, handling all the requests could
|
||||
* cause both a queue of "responses". We would flush them here. However, we
|
||||
* currently just expect each objc block to handle its own response sending
|
||||
* using a `RCTResponseSenderBlock`.
|
||||
*/
|
||||
|
||||
#pragma mark - RCTBridge methods
|
||||
|
||||
/**
|
||||
* Like JS::call, for objective-c.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
||||
{
|
||||
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
|
||||
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
|
||||
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
|
||||
|
||||
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]];
|
||||
}
|
||||
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||
if (scriptLoadError) {
|
||||
onComplete(scriptLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||
method:@"flushedQueue"
|
||||
arguments:@[]
|
||||
callback:^(id json, NSError *error) {
|
||||
[self _handleBuffer:json];
|
||||
onComplete(error);
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"JS_PERF_ENQUEUE" object:nil userInfo:nil];
|
||||
|
||||
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"JS_PERF_DEQUEUE" object:nil userInfo:nil];
|
||||
[self _handleBuffer:json];
|
||||
};
|
||||
|
||||
[_javaScriptExecutor executeJSCall:module
|
||||
method:method
|
||||
arguments:args
|
||||
callback:processResponse];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Processing
|
||||
|
||||
- (void)_handleBuffer:(id)buffer
|
||||
{
|
||||
if (buffer == nil || buffer == (id)kCFNull) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![buffer isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *requestsArray = (NSArray *)buffer;
|
||||
NSUInteger bufferRowCount = [requestsArray count];
|
||||
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
|
||||
if (bufferRowCount != expectedFieldsCount) {
|
||||
RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
|
||||
id field = [requestsArray objectAtIndex:fieldIndex];
|
||||
if (![field isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
|
||||
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
|
||||
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
|
||||
|
||||
NSUInteger numRequests = [moduleIDs count];
|
||||
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
|
||||
if (!allSame) {
|
||||
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSUInteger i = 0; i < numRequests; i++) {
|
||||
@autoreleasepool {
|
||||
[self _handleRequestNumber:i
|
||||
moduleID:[moduleIDs[i] integerValue]
|
||||
methodID:[methodIDs[i] integerValue]
|
||||
params:paramsArrays[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: only used by RCTUIManager - can we eliminate this special case?
|
||||
dispatch_async(self.shadowQueue, ^{
|
||||
for (id module in _modulesByID.allObjects) {
|
||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||
[module batchDidComplete];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)_handleRequestNumber:(NSUInteger)i
|
||||
moduleID:(NSUInteger)moduleID
|
||||
methodID:(NSUInteger)methodID
|
||||
params:(NSArray *)params
|
||||
{
|
||||
if (![params isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Look up method
|
||||
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
|
||||
if (methodID >= methods.count) {
|
||||
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]);
|
||||
return NO;
|
||||
}
|
||||
RCTModuleMethod *method = methods[methodID];
|
||||
|
||||
__weak RCTBridge *weakSelf = self;
|
||||
dispatch_async(self.shadowQueue, ^{
|
||||
__strong RCTBridge *strongSelf = weakSelf;
|
||||
|
||||
if (!strongSelf.isValid) {
|
||||
// strongSelf has been invalidated since the dispatch_async call and this
|
||||
// invocation should not continue.
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up module
|
||||
id module = strongSelf->_modulesByID[moduleID];
|
||||
if (!module) {
|
||||
RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
|
||||
return;
|
||||
}
|
||||
|
||||
@try {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
|
||||
}
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)hasValidJSExecutor
|
||||
{
|
||||
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
|
||||
}
|
||||
|
||||
+ (void)log:(NSArray *)objects level:(NSString *)level
|
||||
{
|
||||
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
|
||||
RCTLogError(@"ERROR: No valid JS executor to log %@.", objects);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *args = [NSMutableArray arrayWithObject:level];
|
||||
|
||||
// TODO (#5906496): Find out and document why we skip the first object
|
||||
for (id ob in [objects subarrayWithRange:(NSRange){1, [objects count] - 1}]) {
|
||||
if ([NSJSONSerialization isValidJSONObject:@[ob]]) {
|
||||
[args addObject:ob];
|
||||
} else {
|
||||
[args addObject:[ob description]];
|
||||
}
|
||||
}
|
||||
|
||||
// Note: the js executor could get invalidated while we're trying to call this...need to watch out for that.
|
||||
[_latestJSExecutor executeJSCall:@"RCTLog"
|
||||
method:@"logIfNoNativeHook"
|
||||
arguments:args
|
||||
callback:^(id json, NSError *error) {}];
|
||||
}
|
||||
|
||||
@end
|
||||
67
React/Base/RCTBridgeModule.h
Normal file
67
React/Base/RCTBridgeModule.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTJSMethodRegistrar.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
/**
|
||||
* The type of a block that is capable of sending a response to a bridged
|
||||
* operation. Use this for returning callback methods to JS.
|
||||
*/
|
||||
typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||
|
||||
/**
|
||||
* Provides the interface needed to register a bridge module.
|
||||
*/
|
||||
@protocol RCTBridgeModule <RCTJSMethodRegistrar>
|
||||
@optional
|
||||
|
||||
/**
|
||||
* A reference to the RCTBridge. Useful for modules that require access
|
||||
* to bridge features, such as sending events or making JS calls. This
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The module name exposed to JS. If omitted, this will be inferred
|
||||
* automatically by using the native module's class name.
|
||||
*/
|
||||
+ (NSString *)moduleName;
|
||||
|
||||
/**
|
||||
* 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) __attribute__((used, section("__DATA,RCTExport" \
|
||||
))) static const char *__rct_export_entry__[] = { __func__, #js_name }
|
||||
|
||||
/**
|
||||
* Injects constants into JS. These constants are made accessible via
|
||||
* NativeModules.ModuleName.X. This method is called when the module is
|
||||
* registered by the bridge. It is only called once for the lifetime of the
|
||||
* bridge, so it is not suitable for returning dynamic values, but may be
|
||||
* used for long-lived values such as session keys, that are regenerated only
|
||||
* as part of a reload of the entire React application.
|
||||
*/
|
||||
- (NSDictionary *)constantsToExport;
|
||||
|
||||
/**
|
||||
* Notifies the module that a batch of JS method invocations has just completed.
|
||||
*/
|
||||
- (void)batchDidComplete;
|
||||
|
||||
@end
|
||||
29
React/Base/RCTCache.h
Normal file
29
React/Base/RCTCache.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface RCTCache : NSObject
|
||||
|
||||
- (instancetype)init; // name = @"default"
|
||||
- (instancetype)initWithName:(NSString *)name;
|
||||
|
||||
@property (nonatomic, assign) NSUInteger maximumDiskSize; // in bytes
|
||||
|
||||
#pragma mark - Retrieval
|
||||
|
||||
- (BOOL)hasDataForKey:(NSString *)key;
|
||||
- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *data))completionHandler;
|
||||
|
||||
#pragma mark - Insertion
|
||||
|
||||
- (void)setData:(NSData *)data forKey:(NSString *)key;
|
||||
- (void)removeAllData;
|
||||
|
||||
@end
|
||||
231
React/Base/RCTCache.m
Normal file
231
React/Base/RCTCache.m
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTCache.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <sys/xattr.h>
|
||||
|
||||
static NSString *const RCTCacheSubdirectoryName = @"React";
|
||||
static NSString *const RCTKeyExtendedAttributeName = @"com.facebook.React.RCTCacheManager.Key";
|
||||
static NSMapTable *RCTLivingCachesByName;
|
||||
|
||||
static NSError *RCTPOSIXError(int errorNumber)
|
||||
{
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @(strerror(errorNumber))
|
||||
};
|
||||
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo];
|
||||
}
|
||||
|
||||
static NSString *RCTGetExtendedAttribute(NSURL *fileURL, NSString *key, NSError **error)
|
||||
{
|
||||
const char *path = fileURL.fileSystemRepresentation;
|
||||
ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0);
|
||||
if (length <= 0) {
|
||||
if (error) *error = RCTPOSIXError(errno);
|
||||
return nil;
|
||||
}
|
||||
|
||||
char *buffer = malloc(length);
|
||||
length = getxattr(path, key.UTF8String, buffer, length, 0, 0);
|
||||
if (length > 0) {
|
||||
return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES];
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
if (error) *error = RCTPOSIXError(errno);
|
||||
return nil;
|
||||
}
|
||||
|
||||
static BOOL RCTSetExtendedAttribute(NSURL *fileURL, NSString *key, NSString *value, NSError **error)
|
||||
{
|
||||
const char *path = fileURL.fileSystemRepresentation;
|
||||
|
||||
int result;
|
||||
if (value) {
|
||||
const char *valueUTF8String = value.UTF8String;
|
||||
result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0);
|
||||
} else {
|
||||
result = removexattr(path, key.UTF8String, 0);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
if (error) *error = RCTPOSIXError(errno);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Cache Record -
|
||||
|
||||
@interface RCTCacheRecord : NSObject
|
||||
|
||||
@property (readonly) NSUUID *UUID;
|
||||
@property (readonly, weak) dispatch_queue_t queue;
|
||||
@property (nonatomic, copy) NSData *data;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTCacheRecord
|
||||
|
||||
- (instancetype)initWithUUID:(NSUUID *)UUID
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_UUID = [UUID copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)enqueueBlock:(dispatch_block_t)block
|
||||
{
|
||||
dispatch_queue_t queue = _queue;
|
||||
if (!queue) {
|
||||
NSString *queueName = [NSString stringWithFormat:@"com.facebook.React.RCTCache.%@", _UUID.UUIDString];
|
||||
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
|
||||
_queue = queue;
|
||||
}
|
||||
|
||||
dispatch_async(queue, block);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Cache
|
||||
|
||||
@implementation RCTCache
|
||||
{
|
||||
NSString *_name;
|
||||
NSFileManager *_fileManager;
|
||||
NSMutableDictionary *_storage;
|
||||
NSURL *_cacheDirectoryURL;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [RCTCache class]) {
|
||||
RCTLivingCachesByName = [NSMapTable strongToWeakObjectsMapTable];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithName:@"default"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
{
|
||||
NSParameterAssert(name.length < NAME_MAX);
|
||||
RCTCache *cachedCache = [RCTLivingCachesByName objectForKey:name];
|
||||
if (cachedCache) {
|
||||
self = cachedCache;
|
||||
return self;
|
||||
}
|
||||
|
||||
if ((self = [super init])) {
|
||||
_name = [name copy];
|
||||
_fileManager = [[NSFileManager alloc] init];
|
||||
_storage = [NSMutableDictionary dictionary];
|
||||
|
||||
NSURL *cacheDirectoryURL = [[_fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:RCTCacheSubdirectoryName isDirectory:YES];
|
||||
_cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:name isDirectory:YES];
|
||||
[_fileManager createDirectoryAtURL:_cacheDirectoryURL withIntermediateDirectories:YES attributes:nil error:NULL];
|
||||
|
||||
NSArray *fileURLs = [_fileManager contentsOfDirectoryAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL];
|
||||
for (NSURL *fileURL in fileURLs) {
|
||||
NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent];
|
||||
if (!UUID) continue;
|
||||
|
||||
NSString *key = RCTGetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, NULL);
|
||||
if (!key) {
|
||||
[_fileManager removeItemAtURL:fileURL error:NULL];
|
||||
continue;
|
||||
}
|
||||
|
||||
_storage[key] = [[RCTCacheRecord alloc] initWithUUID:UUID];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)hasDataForKey:(NSString *)key
|
||||
{
|
||||
return _storage[key] != nil;
|
||||
}
|
||||
|
||||
- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *))completionHandler
|
||||
{
|
||||
NSParameterAssert(key.length > 0);
|
||||
NSParameterAssert(completionHandler != nil);
|
||||
RCTCacheRecord *record = _storage[key];
|
||||
if (!record) {
|
||||
completionHandler(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
[record enqueueBlock:^{
|
||||
if (!record.data) {
|
||||
record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]];
|
||||
}
|
||||
completionHandler(record.data);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setData:(NSData *)data forKey:(NSString *)key
|
||||
{
|
||||
NSParameterAssert(key.length > 0);
|
||||
RCTCacheRecord *record = _storage[key];
|
||||
if (!record) {
|
||||
if (!data) return;
|
||||
|
||||
record = [[RCTCacheRecord alloc] initWithUUID:[NSUUID UUID]];
|
||||
_storage[key] = record;
|
||||
}
|
||||
|
||||
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
|
||||
|
||||
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
|
||||
[record enqueueBlock:^{
|
||||
if (data) {
|
||||
[data writeToURL:fileURL options:NSDataWritingAtomic error:NULL];
|
||||
RCTSetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, key, NULL);
|
||||
} else {
|
||||
[_fileManager removeItemAtURL:fileURL error:NULL];
|
||||
}
|
||||
|
||||
if (identifier != UIBackgroundTaskInvalid) {
|
||||
[[UIApplication sharedApplication] endBackgroundTask:identifier];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeAllData
|
||||
{
|
||||
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
[_storage enumerateKeysAndObjectsUsingBlock:^(NSString *key, RCTCacheRecord *record, BOOL *stop) {
|
||||
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
|
||||
dispatch_group_async(group, record.queue, ^{
|
||||
[_fileManager removeItemAtURL:fileURL error:NULL];
|
||||
});
|
||||
}];
|
||||
|
||||
if (identifier != UIBackgroundTaskInvalid) {
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:identifier];
|
||||
});
|
||||
}
|
||||
|
||||
[_storage removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
256
React/Base/RCTConvert.h
Normal file
256
React/Base/RCTConvert.h
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "Layout.h"
|
||||
#import "RCTAnimationType.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTPointerEvents.h"
|
||||
|
||||
/**
|
||||
* This class provides a collection of conversion functions for mapping
|
||||
* JSON objects to native types and classes. These are useful when writing
|
||||
* custom RCTViewManager setter methods.
|
||||
*/
|
||||
@interface RCTConvert : NSObject
|
||||
|
||||
+ (BOOL)BOOL:(id)json;
|
||||
+ (double)double:(id)json;
|
||||
+ (float)float:(id)json;
|
||||
+ (int)int:(id)json;
|
||||
|
||||
+ (int64_t)int64_t:(id)json;
|
||||
+ (uint64_t)uint64_t:(id)json;
|
||||
|
||||
+ (NSInteger)NSInteger:(id)json;
|
||||
+ (NSUInteger)NSUInteger:(id)json;
|
||||
|
||||
+ (NSArray *)NSArray:(id)json;
|
||||
+ (NSDictionary *)NSDictionary:(id)json;
|
||||
+ (NSString *)NSString:(id)json;
|
||||
+ (NSNumber *)NSNumber:(id)json;
|
||||
|
||||
+ (NSURL *)NSURL:(id)json;
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json;
|
||||
|
||||
+ (NSDate *)NSDate:(id)json;
|
||||
+ (NSTimeZone *)NSTimeZone:(id)json;
|
||||
+ (NSTimeInterval)NSTimeInterval:(id)json;
|
||||
|
||||
+ (NSTextAlignment)NSTextAlignment:(id)json;
|
||||
+ (NSWritingDirection)NSWritingDirection:(id)json;
|
||||
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
|
||||
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
|
||||
+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
|
||||
+ (UIKeyboardType)UIKeyboardType:(id)json;
|
||||
|
||||
+ (UIViewContentMode)UIViewContentMode:(id)json;
|
||||
+ (UIBarStyle)UIBarStyle:(id)json;
|
||||
|
||||
+ (CGFloat)CGFloat:(id)json;
|
||||
+ (CGPoint)CGPoint:(id)json;
|
||||
+ (CGSize)CGSize:(id)json;
|
||||
+ (CGRect)CGRect:(id)json;
|
||||
+ (UIEdgeInsets)UIEdgeInsets:(id)json;
|
||||
|
||||
+ (CGLineCap)CGLineCap:(id)json;
|
||||
+ (CGLineJoin)CGLineJoin:(id)json;
|
||||
|
||||
+ (CATransform3D)CATransform3D:(id)json;
|
||||
+ (CGAffineTransform)CGAffineTransform:(id)json;
|
||||
|
||||
+ (UIColor *)UIColor:(id)json;
|
||||
+ (CGColorRef)CGColor:(id)json;
|
||||
|
||||
+ (UIImage *)UIImage:(id)json;
|
||||
+ (CGImageRef)CGImage:(id)json;
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
|
||||
size:(id)size weight:(id)weight style:(id)style;
|
||||
|
||||
+ (NSArray *)NSStringArray:(id)json;
|
||||
+ (NSArray *)NSDictionaryArray:(id)json;
|
||||
+ (NSArray *)NSURLArray:(id)json;
|
||||
+ (NSArray *)NSNumberArray:(id)json;
|
||||
+ (NSArray *)UIColorArray:(id)json;
|
||||
+ (NSArray *)CGColorArray:(id)json;
|
||||
|
||||
+ (BOOL)css_overflow:(id)json;
|
||||
+ (css_flex_direction_t)css_flex_direction_t:(id)json;
|
||||
+ (css_justify_t)css_justify_t:(id)json;
|
||||
+ (css_align_t)css_align_t:(id)json;
|
||||
+ (css_position_type_t)css_position_type_t:(id)json;
|
||||
+ (css_wrap_type_t)css_wrap_type_t:(id)json;
|
||||
|
||||
+ (RCTPointerEvents)RCTPointerEvents:(id)json;
|
||||
+ (RCTAnimationType)RCTAnimationType:(id)json;
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This function will attempt to set a property using a json value by first
|
||||
* inferring the correct type from all available information, and then
|
||||
* applying an appropriate conversion method. If the property does not
|
||||
* exist, or the type cannot be inferred, the function will return NO.
|
||||
*/
|
||||
BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
|
||||
|
||||
/**
|
||||
* This function attempts to copy a property from the source object to the
|
||||
* destination object using KVC. If the property does not exist, or cannot
|
||||
* be set, it will do nothing and return NO.
|
||||
*/
|
||||
BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This macro is used for creating converter functions with arbitrary logic.
|
||||
*/
|
||||
#define RCT_CONVERTER_CUSTOM(type, name, code) \
|
||||
+ (type)name:(id)json \
|
||||
{ \
|
||||
if (json == [NSNull null]) { \
|
||||
json = nil; \
|
||||
} \
|
||||
@try { \
|
||||
return code; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
||||
json, [json class], #type); \
|
||||
json = nil; \
|
||||
return code; \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* This macro is used for creating simple converter functions that just call
|
||||
* the specified getter method on the json value.
|
||||
*/
|
||||
#define RCT_CONVERTER(type, name, getter) \
|
||||
RCT_CONVERTER_CUSTOM(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.
|
||||
*/
|
||||
#define RCT_NUMBER_CONVERTER(type, getter) \
|
||||
RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
|
||||
|
||||
/**
|
||||
* This macro is used for creating converters for enum types.
|
||||
*/
|
||||
#define RCT_ENUM_CONVERTER(type, values, default, getter) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
static NSDictionary *mapping; \
|
||||
static dispatch_once_t onceToken; \
|
||||
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 class], 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; \
|
||||
}
|
||||
|
||||
/**
|
||||
* This macro is used for creating converter functions for structs that consist
|
||||
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
|
||||
*/
|
||||
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
@try { \
|
||||
static NSArray *fields; \
|
||||
static NSUInteger count; \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
fields = values; \
|
||||
count = [fields count]; \
|
||||
}); \
|
||||
type result; \
|
||||
if ([json isKindOfClass:[NSArray class]]) { \
|
||||
if ([json count] != count) { \
|
||||
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
|
||||
} \
|
||||
} \
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) { \
|
||||
NSDictionary *aliases = _aliases; \
|
||||
if (aliases.count) { \
|
||||
json = [json mutableCopy]; \
|
||||
for (NSString *alias in aliases) { \
|
||||
NSString *key = aliases[alias]; \
|
||||
NSNumber *number = json[key]; \
|
||||
if (number) { \
|
||||
((NSMutableDictionary *)json)[key] = number; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
|
||||
} \
|
||||
} else if (json && json != [NSNull null]) { \
|
||||
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
|
||||
} \
|
||||
return result; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
|
||||
type result; \
|
||||
return result; \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* This macro is used for creating converter functions for typed arrays.
|
||||
*/
|
||||
#define RCT_ARRAY_CONVERTER(type) \
|
||||
+ (NSArray *)type##Array:(id)json \
|
||||
{ \
|
||||
NSMutableArray *values = [[NSMutableArray alloc] init]; \
|
||||
for (id jsonValue in [self NSArray:json]) { \
|
||||
id value = [self type:jsonValue]; \
|
||||
if (value) { \
|
||||
[values addObject:value]; \
|
||||
} \
|
||||
} \
|
||||
return values; \
|
||||
}
|
||||
770
React/Base/RCTConvert.m
Normal file
770
React/Base/RCTConvert.m
Normal file
@@ -0,0 +1,770 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
@implementation RCTConvert
|
||||
|
||||
RCT_CONVERTER(BOOL, BOOL, boolValue)
|
||||
RCT_NUMBER_CONVERTER(double, doubleValue)
|
||||
RCT_NUMBER_CONVERTER(float, floatValue)
|
||||
RCT_NUMBER_CONVERTER(int, intValue)
|
||||
|
||||
RCT_NUMBER_CONVERTER(int64_t, longLongValue);
|
||||
RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue);
|
||||
|
||||
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
|
||||
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
|
||||
|
||||
RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
|
||||
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
|
||||
RCT_CONVERTER(NSString *, NSString, description)
|
||||
|
||||
+ (NSNumber *)NSNumber:(id)json
|
||||
{
|
||||
if ([json isKindOfClass:[NSNumber class]]) {
|
||||
return json;
|
||||
} else if ([json isKindOfClass:[NSString class]]) {
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
NSNumber *number = [formatter numberFromString:json];
|
||||
if (!number) {
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
|
||||
}
|
||||
return number;
|
||||
} else if (json && json != [NSNull null]) {
|
||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json class]);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json class], json);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *path = json;
|
||||
if ([path isAbsolutePath])
|
||||
{
|
||||
return [NSURL fileURLWithPath:path];
|
||||
}
|
||||
else if ([path length])
|
||||
{
|
||||
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
|
||||
if ([URL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) {
|
||||
RCTLogWarn(@"The file '%@' does not exist", URL);
|
||||
return nil;
|
||||
}
|
||||
return URL;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json
|
||||
{
|
||||
return [NSURLRequest requestWithURL:[self NSURL:json]];
|
||||
}
|
||||
|
||||
// JS Standard for time is milliseconds
|
||||
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[self double:json] / 1000.0])
|
||||
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
|
||||
|
||||
// JS standard for time zones is minutes.
|
||||
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
|
||||
|
||||
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
|
||||
@"auto": @(NSTextAlignmentNatural),
|
||||
@"left": @(NSTextAlignmentLeft),
|
||||
@"center": @(NSTextAlignmentCenter),
|
||||
@"right": @(NSTextAlignmentRight),
|
||||
@"justify": @(NSTextAlignmentJustified),
|
||||
}), NSTextAlignmentNatural, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(NSWritingDirection, (@{
|
||||
@"auto": @(NSWritingDirectionNatural),
|
||||
@"ltr": @(NSWritingDirectionLeftToRight),
|
||||
@"rtl": @(NSWritingDirectionRightToLeft),
|
||||
}), NSWritingDirectionNatural, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UITextAutocapitalizationType, (@{
|
||||
@"none": @(UITextAutocapitalizationTypeNone),
|
||||
@"words": @(UITextAutocapitalizationTypeWords),
|
||||
@"sentences": @(UITextAutocapitalizationTypeSentences),
|
||||
@"characters": @(UITextAutocapitalizationTypeAllCharacters)
|
||||
}), UITextAutocapitalizationTypeSentences, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{
|
||||
@"never": @(UITextFieldViewModeNever),
|
||||
@"while-editing": @(UITextFieldViewModeWhileEditing),
|
||||
@"unless-editing": @(UITextFieldViewModeUnlessEditing),
|
||||
@"always": @(UITextFieldViewModeAlways),
|
||||
}), UITextFieldViewModeNever, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
|
||||
@"none": @(UIScrollViewKeyboardDismissModeNone),
|
||||
@"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
|
||||
@"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
|
||||
}), UIScrollViewKeyboardDismissModeNone, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UIKeyboardType, (@{
|
||||
@"numeric": @(UIKeyboardTypeDecimalPad),
|
||||
@"default": @(UIKeyboardTypeDefault),
|
||||
}), UIKeyboardTypeDefault, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UIViewContentMode, (@{
|
||||
@"scale-to-fill": @(UIViewContentModeScaleToFill),
|
||||
@"scale-aspect-fit": @(UIViewContentModeScaleAspectFit),
|
||||
@"scale-aspect-fill": @(UIViewContentModeScaleAspectFill),
|
||||
@"redraw": @(UIViewContentModeRedraw),
|
||||
@"center": @(UIViewContentModeCenter),
|
||||
@"top": @(UIViewContentModeTop),
|
||||
@"bottom": @(UIViewContentModeBottom),
|
||||
@"left": @(UIViewContentModeLeft),
|
||||
@"right": @(UIViewContentModeRight),
|
||||
@"top-left": @(UIViewContentModeTopLeft),
|
||||
@"top-right": @(UIViewContentModeTopRight),
|
||||
@"bottom-left": @(UIViewContentModeBottomLeft),
|
||||
@"bottom-right": @(UIViewContentModeBottomRight),
|
||||
}), UIViewContentModeScaleToFill, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UIBarStyle, (@{
|
||||
@"default": @(UIBarStyleDefault),
|
||||
@"black": @(UIBarStyleBlack),
|
||||
}), UIBarStyleDefault, integerValue)
|
||||
|
||||
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
|
||||
RCT_CONVERTER_CUSTOM(CGFloat, CGFloat, [self double:json])
|
||||
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
|
||||
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]), nil)
|
||||
|
||||
RCT_ENUM_CONVERTER(CGLineJoin, (@{
|
||||
@"miter": @(kCGLineJoinMiter),
|
||||
@"round": @(kCGLineJoinRound),
|
||||
@"bevel": @(kCGLineJoinBevel),
|
||||
}), kCGLineJoinMiter, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(CGLineCap, (@{
|
||||
@"butt": @(kCGLineCapButt),
|
||||
@"round": @(kCGLineCapRound),
|
||||
@"square": @(kCGLineCapSquare),
|
||||
}), kCGLineCapButt, intValue)
|
||||
|
||||
RCT_CGSTRUCT_CONVERTER(CATransform3D, (@[
|
||||
@"m11", @"m12", @"m13", @"m14",
|
||||
@"m21", @"m22", @"m23", @"m24",
|
||||
@"m31", @"m32", @"m33", @"m34",
|
||||
@"m41", @"m42", @"m43", @"m44"
|
||||
]), nil)
|
||||
|
||||
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||
@"a", @"b", @"c", @"d", @"tx", @"ty"
|
||||
]), nil)
|
||||
|
||||
+ (UIColor *)UIColor:(id)json
|
||||
{
|
||||
// Check color cache
|
||||
static NSMutableDictionary *colorCache = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorCache = [[NSMutableDictionary alloc] init];
|
||||
});
|
||||
UIColor *color = colorCache[json];
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
|
||||
if ([json isKindOfClass:[NSString class]]) {
|
||||
|
||||
// Check named colors
|
||||
static NSDictionary *namedColors = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
namedColors = @{
|
||||
|
||||
// CSS colors
|
||||
@"aliceblue": @"#f0f8ff",
|
||||
@"antiquewhite": @"#faebd7",
|
||||
@"aqua": @"#00ffff",
|
||||
@"aquamarine": @"#7fffd4",
|
||||
@"azure": @"#f0ffff",
|
||||
@"beige": @"#f5f5dc",
|
||||
@"bisque": @"#ffe4c4",
|
||||
@"black": @"#000000",
|
||||
@"blanchedalmond": @"#ffebcd",
|
||||
@"blue": @"#0000ff",
|
||||
@"blueviolet": @"#8a2be2",
|
||||
@"brown": @"#a52a2a",
|
||||
@"burlywood": @"#deb887",
|
||||
@"cadetblue": @"#5f9ea0",
|
||||
@"chartreuse": @"#7fff00",
|
||||
@"chocolate": @"#d2691e",
|
||||
@"coral": @"#ff7f50",
|
||||
@"cornflowerblue": @"#6495ed",
|
||||
@"cornsilk": @"#fff8dc",
|
||||
@"crimson": @"#dc143c",
|
||||
@"cyan": @"#00ffff",
|
||||
@"darkblue": @"#00008b",
|
||||
@"darkcyan": @"#008b8b",
|
||||
@"darkgoldenrod": @"#b8860b",
|
||||
@"darkgray": @"#a9a9a9",
|
||||
@"darkgrey": @"#a9a9a9",
|
||||
@"darkgreen": @"#006400",
|
||||
@"darkkhaki": @"#bdb76b",
|
||||
@"darkmagenta": @"#8b008b",
|
||||
@"darkolivegreen": @"#556b2f",
|
||||
@"darkorange": @"#ff8c00",
|
||||
@"darkorchid": @"#9932cc",
|
||||
@"darkred": @"#8b0000",
|
||||
@"darksalmon": @"#e9967a",
|
||||
@"darkseagreen": @"#8fbc8f",
|
||||
@"darkslateblue": @"#483d8b",
|
||||
@"darkslategray": @"#2f4f4f",
|
||||
@"darkslategrey": @"#2f4f4f",
|
||||
@"darkturquoise": @"#00ced1",
|
||||
@"darkviolet": @"#9400d3",
|
||||
@"deeppink": @"#ff1493",
|
||||
@"deepskyblue": @"#00bfff",
|
||||
@"dimgray": @"#696969",
|
||||
@"dimgrey": @"#696969",
|
||||
@"dodgerblue": @"#1e90ff",
|
||||
@"firebrick": @"#b22222",
|
||||
@"floralwhite": @"#fffaf0",
|
||||
@"forestgreen": @"#228b22",
|
||||
@"fuchsia": @"#ff00ff",
|
||||
@"gainsboro": @"#dcdcdc",
|
||||
@"ghostwhite": @"#f8f8ff",
|
||||
@"gold": @"#ffd700",
|
||||
@"goldenrod": @"#daa520",
|
||||
@"gray": @"#808080",
|
||||
@"grey": @"#808080",
|
||||
@"green": @"#008000",
|
||||
@"greenyellow": @"#adff2f",
|
||||
@"honeydew": @"#f0fff0",
|
||||
@"hotpink": @"#ff69b4",
|
||||
@"indianred": @"#cd5c5c",
|
||||
@"indigo": @"#4b0082",
|
||||
@"ivory": @"#fffff0",
|
||||
@"khaki": @"#f0e68c",
|
||||
@"lavender": @"#e6e6fa",
|
||||
@"lavenderblush": @"#fff0f5",
|
||||
@"lawngreen": @"#7cfc00",
|
||||
@"lemonchiffon": @"#fffacd",
|
||||
@"lightblue": @"#add8e6",
|
||||
@"lightcoral": @"#f08080",
|
||||
@"lightcyan": @"#e0ffff",
|
||||
@"lightgoldenrodyellow": @"#fafad2",
|
||||
@"lightgray": @"#d3d3d3",
|
||||
@"lightgrey": @"#d3d3d3",
|
||||
@"lightgreen": @"#90ee90",
|
||||
@"lightpink": @"#ffb6c1",
|
||||
@"lightsalmon": @"#ffa07a",
|
||||
@"lightseagreen": @"#20b2aa",
|
||||
@"lightskyblue": @"#87cefa",
|
||||
@"lightslategray": @"#778899",
|
||||
@"lightslategrey": @"#778899",
|
||||
@"lightsteelblue": @"#b0c4de",
|
||||
@"lightyellow": @"#ffffe0",
|
||||
@"lime": @"#00ff00",
|
||||
@"limegreen": @"#32cd32",
|
||||
@"linen": @"#faf0e6",
|
||||
@"magenta": @"#ff00ff",
|
||||
@"maroon": @"#800000",
|
||||
@"mediumaquamarine": @"#66cdaa",
|
||||
@"mediumblue": @"#0000cd",
|
||||
@"mediumorchid": @"#ba55d3",
|
||||
@"mediumpurple": @"#9370db",
|
||||
@"mediumseagreen": @"#3cb371",
|
||||
@"mediumslateblue": @"#7b68ee",
|
||||
@"mediumspringgreen": @"#00fa9a",
|
||||
@"mediumturquoise": @"#48d1cc",
|
||||
@"mediumvioletred": @"#c71585",
|
||||
@"midnightblue": @"#191970",
|
||||
@"mintcream": @"#f5fffa",
|
||||
@"mistyrose": @"#ffe4e1",
|
||||
@"moccasin": @"#ffe4b5",
|
||||
@"navajowhite": @"#ffdead",
|
||||
@"navy": @"#000080",
|
||||
@"oldlace": @"#fdf5e6",
|
||||
@"olive": @"#808000",
|
||||
@"olivedrab": @"#6b8e23",
|
||||
@"orange": @"#ffa500",
|
||||
@"orangered": @"#ff4500",
|
||||
@"orchid": @"#da70d6",
|
||||
@"palegoldenrod": @"#eee8aa",
|
||||
@"palegreen": @"#98fb98",
|
||||
@"paleturquoise": @"#afeeee",
|
||||
@"palevioletred": @"#db7093",
|
||||
@"papayawhip": @"#ffefd5",
|
||||
@"peachpuff": @"#ffdab9",
|
||||
@"peru": @"#cd853f",
|
||||
@"pink": @"#ffc0cb",
|
||||
@"plum": @"#dda0dd",
|
||||
@"powderblue": @"#b0e0e6",
|
||||
@"purple": @"#800080",
|
||||
@"rebeccapurple": @"#663399",
|
||||
@"red": @"#ff0000",
|
||||
@"rosybrown": @"#bc8f8f",
|
||||
@"royalblue": @"#4169e1",
|
||||
@"saddlebrown": @"#8b4513",
|
||||
@"salmon": @"#fa8072",
|
||||
@"sandybrown": @"#f4a460",
|
||||
@"seagreen": @"#2e8b57",
|
||||
@"seashell": @"#fff5ee",
|
||||
@"sienna": @"#a0522d",
|
||||
@"silver": @"#c0c0c0",
|
||||
@"skyblue": @"#87ceeb",
|
||||
@"slateblue": @"#6a5acd",
|
||||
@"slategray": @"#708090",
|
||||
@"slategrey": @"#708090",
|
||||
@"snow": @"#fffafa",
|
||||
@"springgreen": @"#00ff7f",
|
||||
@"steelblue": @"#4682b4",
|
||||
@"tan": @"#d2b48c",
|
||||
@"teal": @"#008080",
|
||||
@"thistle": @"#d8bfd8",
|
||||
@"tomato": @"#ff6347",
|
||||
@"turquoise": @"#40e0d0",
|
||||
@"violet": @"#ee82ee",
|
||||
@"wheat": @"#f5deb3",
|
||||
@"white": @"#ffffff",
|
||||
@"whitesmoke": @"#f5f5f5",
|
||||
@"yellow": @"#ffff00",
|
||||
@"yellowgreen": @"#9acd32",
|
||||
|
||||
// Nonstandard color extensions
|
||||
@"transparent": @"rgba(0,0,0,0)",
|
||||
};
|
||||
});
|
||||
NSString *colorString = namedColors[json];
|
||||
if (!colorString) {
|
||||
colorString = json;
|
||||
}
|
||||
|
||||
// Parse color
|
||||
NSUInteger red = -1;
|
||||
NSUInteger green = -1;
|
||||
NSUInteger blue = -1;
|
||||
CGFloat alpha = 1.0;
|
||||
if ([colorString hasPrefix:@"#"]) {
|
||||
sscanf([colorString UTF8String], "#%02tX%02tX%02tX", &red, &green, &blue);
|
||||
} else if ([colorString hasPrefix:@"rgba("]) {
|
||||
double tmpAlpha;
|
||||
sscanf([colorString UTF8String], "rgba(%zd,%zd,%zd,%lf)", &red, &green, &blue, &tmpAlpha);
|
||||
alpha = tmpAlpha > 0.99 ? 1.0 : tmpAlpha;
|
||||
} else if ([colorString hasPrefix:@"rgb("]) {
|
||||
sscanf([colorString UTF8String], "rgb(%zd,%zd,%zd)", &red, &green, &blue);
|
||||
} else {
|
||||
RCTLogError(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb", colorString);
|
||||
}
|
||||
if (red == -1 || green == -1 || blue == -1 || alpha > 1.0 || alpha < 0.0) {
|
||||
RCTLogError(@"Invalid color string '%@'", colorString);
|
||||
} else {
|
||||
color = [UIColor colorWithRed:red / 255.0 green:green / 255.0 blue:blue / 255.0 alpha:alpha];
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSArray class]]) {
|
||||
|
||||
if ([json count] < 3 || [json count] > 4) {
|
||||
|
||||
RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
|
||||
|
||||
} else {
|
||||
|
||||
// Color array
|
||||
color = [UIColor colorWithRed:[self double:json[0]]
|
||||
green:[self double:json[1]]
|
||||
blue:[self double:json[2]]
|
||||
alpha:[json count] > 3 ? [self double:json[3]] : 1];
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
// Color dictionary
|
||||
color = [UIColor colorWithRed:[self double:json[@"r"]]
|
||||
green:[self double:json[@"g"]]
|
||||
blue:[self double:json[@"b"]]
|
||||
alpha:[self double:json[@"a"] ?: @1]];
|
||||
|
||||
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
||||
|
||||
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, \
|
||||
received %@: %@", [json class], json);
|
||||
}
|
||||
|
||||
// Default color
|
||||
if (!color) {
|
||||
color = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
// Cache and return
|
||||
if (json) {
|
||||
colorCache[json] = color;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
+ (CGColorRef)CGColor:(id)json
|
||||
{
|
||||
return [self UIColor:json].CGColor;
|
||||
}
|
||||
|
||||
+ (UIImage *)UIImage:(id)json
|
||||
{
|
||||
// TODO: we might as well cache the result of these checks (and possibly the
|
||||
// image itself) so as to reduce overhead on subsequent checks of the same input
|
||||
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json class], json);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([json length] == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image = nil;
|
||||
NSString *path = json;
|
||||
if ([path isAbsolutePath]) {
|
||||
image = [UIImage imageWithContentsOfFile:path];
|
||||
} else {
|
||||
image = [UIImage imageNamed:path];
|
||||
if (!image) {
|
||||
image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]];
|
||||
}
|
||||
}
|
||||
// NOTE: we don't warn about nil images because there are legitimate
|
||||
// case where we find out if a string is an image by using this method
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (CGImageRef)CGImage:(id)json
|
||||
{
|
||||
return [self UIImage:json].CGImage;
|
||||
}
|
||||
|
||||
#ifndef __IPHONE_8_2
|
||||
|
||||
// These constants are defined in iPhone SDK 8.2
|
||||
// They'll work fine in earlier iOS versions, but the app cannot be built with
|
||||
// an SDK version < 8.2 unless we redefine them here. This will be removed
|
||||
// in a future version of React, once 8.2 is more widely adopted.
|
||||
|
||||
static const CGFloat UIFontWeightUltraLight = -0.8;
|
||||
static const CGFloat UIFontWeightThin = -0.6;
|
||||
static const CGFloat UIFontWeightLight = -0.4;
|
||||
static const CGFloat UIFontWeightRegular = 0;
|
||||
static const CGFloat UIFontWeightMedium = 0.23;
|
||||
static const CGFloat UIFontWeightSemibold = 0.3;
|
||||
static const CGFloat UIFontWeightBold = 0.4;
|
||||
static const CGFloat UIFontWeightHeavy = 0.56;
|
||||
static const CGFloat UIFontWeightBlack = 0.62;
|
||||
|
||||
#endif
|
||||
|
||||
typedef CGFloat RCTFontWeight;
|
||||
RCT_ENUM_CONVERTER(RCTFontWeight, (@{
|
||||
@"normal": @(UIFontWeightRegular),
|
||||
@"bold": @(UIFontWeightBold),
|
||||
@"100": @(UIFontWeightUltraLight),
|
||||
@"200": @(UIFontWeightThin),
|
||||
@"300": @(UIFontWeightLight),
|
||||
@"400": @(UIFontWeightRegular),
|
||||
@"500": @(UIFontWeightMedium),
|
||||
@"600": @(UIFontWeightSemibold),
|
||||
@"700": @(UIFontWeightBold),
|
||||
@"800": @(UIFontWeightHeavy),
|
||||
@"900": @(UIFontWeightBlack),
|
||||
}), UIFontWeightRegular, doubleValue)
|
||||
|
||||
typedef BOOL RCTFontStyle;
|
||||
RCT_ENUM_CONVERTER(RCTFontStyle, (@{
|
||||
@"normal": @NO,
|
||||
@"italic": @YES,
|
||||
@"oblique": @YES,
|
||||
}), NO, boolValue)
|
||||
|
||||
static RCTFontWeight RCTWeightOfFont(UIFont *font)
|
||||
{
|
||||
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
|
||||
return [traits[UIFontWeightTrait] doubleValue];
|
||||
}
|
||||
|
||||
static BOOL RCTFontIsItalic(UIFont *font)
|
||||
{
|
||||
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
|
||||
UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
|
||||
return (symbolicTraits & UIFontDescriptorTraitItalic) != 0;
|
||||
}
|
||||
|
||||
static BOOL RCTFontIsCondensed(UIFont *font)
|
||||
{
|
||||
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
|
||||
UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
|
||||
return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0;
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:nil size:json weight:nil style:nil];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:nil size:nil weight:json style:nil];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:nil size:nil weight:nil style:json];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:json size:nil weight:nil style:nil];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
|
||||
size:(id)size weight:(id)weight style:(id)style
|
||||
{
|
||||
// Defaults
|
||||
NSString *const RCTDefaultFontFamily = @"Helvetica Neue";
|
||||
const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular;
|
||||
const CGFloat RCTDefaultFontSize = 14;
|
||||
|
||||
// Get existing properties
|
||||
BOOL isItalic = NO;
|
||||
BOOL isCondensed = NO;
|
||||
RCTFontWeight fontWeight = RCTDefaultFontWeight;
|
||||
if (font) {
|
||||
family = font.familyName;
|
||||
fontWeight = RCTWeightOfFont(font);
|
||||
isItalic = RCTFontIsItalic(font);
|
||||
isCondensed = RCTFontIsCondensed(font);
|
||||
}
|
||||
|
||||
// Get font weight
|
||||
if (weight) {
|
||||
fontWeight = [self RCTFontWeight:weight];
|
||||
}
|
||||
|
||||
// Get font style
|
||||
if (style) {
|
||||
isItalic = [self RCTFontStyle:style];
|
||||
}
|
||||
|
||||
// Get font size
|
||||
CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize;
|
||||
|
||||
// Get font family
|
||||
NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily;
|
||||
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
|
||||
font = [UIFont fontWithName:familyName size:fontSize];
|
||||
if (font) {
|
||||
// It's actually a font name, not a font family name,
|
||||
// but we'll do what was meant, not what was said.
|
||||
familyName = font.familyName;
|
||||
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
|
||||
fontWeight = [traits[UIFontWeightTrait] doubleValue];
|
||||
} else {
|
||||
// Not a valid font or family
|
||||
RCTLogError(@"Unrecognized font family '%@'", familyName);
|
||||
familyName = RCTDefaultFontFamily;
|
||||
}
|
||||
}
|
||||
|
||||
// Get closest match
|
||||
UIFont *bestMatch = font;
|
||||
CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY;
|
||||
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
|
||||
UIFont *match = [UIFont fontWithName:name size:fontSize];
|
||||
if (isItalic == RCTFontIsItalic(match) &&
|
||||
isCondensed == RCTFontIsCondensed(match)) {
|
||||
CGFloat testWeight = RCTWeightOfFont(match);
|
||||
if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) {
|
||||
bestMatch = match;
|
||||
closestWeight = testWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safety net
|
||||
if (!bestMatch) {
|
||||
RCTLogError(@"Could not find font with family: '%@', size: %@, \
|
||||
weight: %@, style: %@", family, size, weight, style);
|
||||
bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject]
|
||||
size:fontSize];
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
RCT_ARRAY_CONVERTER(NSString)
|
||||
RCT_ARRAY_CONVERTER(NSDictionary)
|
||||
RCT_ARRAY_CONVERTER(NSURL)
|
||||
RCT_ARRAY_CONVERTER(NSNumber)
|
||||
RCT_ARRAY_CONVERTER(UIColor)
|
||||
|
||||
// Can't use RCT_ARRAY_CONVERTER due to bridged cast
|
||||
+ (NSArray *)CGColorArray:(id)json
|
||||
{
|
||||
NSMutableArray *colors = [[NSMutableArray alloc] init];
|
||||
for (id value in [self NSArray:json]) {
|
||||
[colors addObject:(__bridge id)[self CGColor:value]];
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
typedef BOOL css_overflow;
|
||||
|
||||
RCT_ENUM_CONVERTER(css_overflow, (@{
|
||||
@"hidden": @NO,
|
||||
@"visible": @YES
|
||||
}), YES, boolValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_flex_direction_t, (@{
|
||||
@"row": @(CSS_FLEX_DIRECTION_ROW),
|
||||
@"column": @(CSS_FLEX_DIRECTION_COLUMN)
|
||||
}), CSS_FLEX_DIRECTION_COLUMN, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_justify_t, (@{
|
||||
@"flex-start": @(CSS_JUSTIFY_FLEX_START),
|
||||
@"flex-end": @(CSS_JUSTIFY_FLEX_END),
|
||||
@"center": @(CSS_JUSTIFY_CENTER),
|
||||
@"space-between": @(CSS_JUSTIFY_SPACE_BETWEEN),
|
||||
@"space-around": @(CSS_JUSTIFY_SPACE_AROUND)
|
||||
}), CSS_JUSTIFY_FLEX_START, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_align_t, (@{
|
||||
@"flex-start": @(CSS_ALIGN_FLEX_START),
|
||||
@"flex-end": @(CSS_ALIGN_FLEX_END),
|
||||
@"center": @(CSS_ALIGN_CENTER),
|
||||
@"auto": @(CSS_ALIGN_AUTO),
|
||||
@"stretch": @(CSS_ALIGN_STRETCH)
|
||||
}), CSS_ALIGN_FLEX_START, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_position_type_t, (@{
|
||||
@"absolute": @(CSS_POSITION_ABSOLUTE),
|
||||
@"relative": @(CSS_POSITION_RELATIVE)
|
||||
}), CSS_POSITION_RELATIVE, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_wrap_type_t, (@{
|
||||
@"wrap": @(CSS_WRAP),
|
||||
@"nowrap": @(CSS_NOWRAP)
|
||||
}), CSS_NOWRAP, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(RCTPointerEvents, (@{
|
||||
@"none": @(RCTPointerEventsNone),
|
||||
@"box-only": @(RCTPointerEventsBoxOnly),
|
||||
@"box-none": @(RCTPointerEventsBoxNone),
|
||||
@"auto": @(RCTPointerEventsUnspecified)
|
||||
}), RCTPointerEventsUnspecified, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(RCTAnimationType, (@{
|
||||
@"spring": @(RCTAnimationTypeSpring),
|
||||
@"linear": @(RCTAnimationTypeLinear),
|
||||
@"easeIn": @(RCTAnimationTypeEaseIn),
|
||||
@"easeOut": @(RCTAnimationTypeEaseOut),
|
||||
@"easeInEaseOut": @(RCTAnimationTypeEaseInEaseOut),
|
||||
}), RCTAnimationTypeEaseInEaseOut, integerValue)
|
||||
|
||||
@end
|
||||
|
||||
BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json)
|
||||
{
|
||||
// Split keypath
|
||||
NSArray *parts = [keyPath componentsSeparatedByString:@"."];
|
||||
NSString *key = [parts lastObject];
|
||||
for (NSUInteger i = 0; i < parts.count - 1; i++) {
|
||||
target = [target valueForKey:parts[i]];
|
||||
if (!target) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Get property setter
|
||||
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
|
||||
[[key substringToIndex:1] uppercaseString],
|
||||
[key substringFromIndex:1]]);
|
||||
|
||||
// Fail early
|
||||
if (![target respondsToSelector:setter]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Get converted value
|
||||
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[invocation setArgument:&type atIndex:1];
|
||||
[invocation setArgument:&json atIndex:2];
|
||||
[invocation invokeWithTarget:[RCTConvert class]];
|
||||
NSUInteger length = [signature methodReturnLength];
|
||||
void *value = malloc(length);
|
||||
[invocation getReturnValue:value];
|
||||
|
||||
// Set converted value
|
||||
signature = [target methodSignatureForSelector:setter];
|
||||
invocation = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[invocation setArgument:&setter atIndex:1];
|
||||
[invocation setArgument:value atIndex:2];
|
||||
[invocation invokeWithTarget:target];
|
||||
free(value);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
|
||||
{
|
||||
// Split keypath
|
||||
NSArray *parts = [keyPath componentsSeparatedByString:@"."];
|
||||
NSString *key = [parts lastObject];
|
||||
for (NSUInteger i = 0; i < parts.count - 1; i++) {
|
||||
source = [source valueForKey:parts[i]];
|
||||
target = [target valueForKey:parts[i]];
|
||||
if (!source || !target) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Get property getter
|
||||
SEL getter = NSSelectorFromString(key);
|
||||
|
||||
// Get property setter
|
||||
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
|
||||
[[key substringToIndex:1] uppercaseString],
|
||||
[key substringFromIndex:1]]);
|
||||
|
||||
// Fail early
|
||||
if (![source respondsToSelector:getter] || ![target respondsToSelector:setter]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Get converted value
|
||||
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[invocation setArgument:&getter atIndex:1];
|
||||
[invocation invokeWithTarget:source];
|
||||
NSUInteger length = [signature methodReturnLength];
|
||||
void *value = malloc(length);
|
||||
[invocation getReturnValue:value];
|
||||
|
||||
// Set converted value
|
||||
signature = [target methodSignatureForSelector:setter];
|
||||
invocation = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[invocation setArgument:&setter atIndex:1];
|
||||
[invocation setArgument:value atIndex:2];
|
||||
[invocation invokeWithTarget:target];
|
||||
free(value);
|
||||
|
||||
return YES;
|
||||
}
|
||||
19
React/Base/RCTDevMenu.h
Normal file
19
React/Base/RCTDevMenu.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTRootView;
|
||||
|
||||
@interface RCTDevMenu : NSObject
|
||||
|
||||
- (instancetype)initWithRootView:(RCTRootView *)rootView;
|
||||
- (void)show;
|
||||
|
||||
@end
|
||||
84
React/Base/RCTDevMenu.m
Normal file
84
React/Base/RCTDevMenu.m
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTDevMenu.h"
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTRootView.h"
|
||||
|
||||
@interface RCTDevMenu () <UIActionSheetDelegate> {
|
||||
BOOL _liveReload;
|
||||
}
|
||||
|
||||
@property (nonatomic, weak) RCTRootView *view;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTDevMenu
|
||||
|
||||
- (instancetype)initWithRootView:(RCTRootView *)rootView
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.view = rootView;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
NSString *debugTitle = self.view.executorClass == nil ? @"Enable Debugging" : @"Disable Debugging";
|
||||
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil];
|
||||
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
||||
[actionSheet showInView:self.view];
|
||||
}
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
if (buttonIndex == 0) {
|
||||
[self.view reload];
|
||||
} else if (buttonIndex == 1) {
|
||||
self.view.executorClass = self.view.executorClass == nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil;
|
||||
[self.view reload];
|
||||
} else if (buttonIndex == 2) {
|
||||
_liveReload = !_liveReload;
|
||||
[self _pollAndReload];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_pollAndReload
|
||||
{
|
||||
if (_liveReload) {
|
||||
NSURL *url = [self.view scriptURL];
|
||||
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
|
||||
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_checkForUpdates:(NSURL *)URL
|
||||
{
|
||||
NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||
longPollRequest.timeoutInterval = 30;
|
||||
NSHTTPURLResponse *response;
|
||||
[NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_liveReload && response.statusCode == 205) {
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
[self.view reload];
|
||||
}
|
||||
[self _pollAndReload];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
73
React/Base/RCTEventDispatcher.h
Normal file
73
React/Base/RCTEventDispatcher.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTTextEventType) {
|
||||
RCTTextEventTypeFocus,
|
||||
RCTTextEventTypeBlur,
|
||||
RCTTextEventTypeChange,
|
||||
RCTTextEventTypeSubmit,
|
||||
RCTTextEventTypeEnd
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTScrollEventType) {
|
||||
RCTScrollEventTypeStart,
|
||||
RCTScrollEventTypeMove,
|
||||
RCTScrollEventTypeEnd,
|
||||
RCTScrollEventTypeStartDeceleration,
|
||||
RCTScrollEventTypeEndDeceleration,
|
||||
RCTScrollEventTypeEndAnimation,
|
||||
};
|
||||
|
||||
/**
|
||||
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
|
||||
* provides some convenience methods for generating event calls.
|
||||
*/
|
||||
@interface RCTEventDispatcher : NSObject
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
/**
|
||||
* Send an application-specific event that does not relate to a specific
|
||||
* view, e.g. a navigation or data update notification.
|
||||
*/
|
||||
- (void)sendAppEventWithName:(NSString *)name body:(id)body;
|
||||
|
||||
/**
|
||||
* Send a device or iOS event that does not relate to a specific view,
|
||||
* e.g.rotation, location, keyboard show/hide, background/awake, etc.
|
||||
*/
|
||||
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body;
|
||||
|
||||
/**
|
||||
* Send a user input event. The body dictionary must contain a "target"
|
||||
* parameter, representing the react tag of the view sending the event
|
||||
*/
|
||||
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;
|
||||
|
||||
/**
|
||||
* Send a text input/focus event.
|
||||
*/
|
||||
- (void)sendTextEventWithType:(RCTTextEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
text:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Send a scroll event.
|
||||
* (You can send a fake scroll event by passing nil for scrollView).
|
||||
*/
|
||||
- (void)sendScrollEventWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData;
|
||||
|
||||
@end
|
||||
126
React/Base/RCTEventDispatcher.m
Normal file
126
React/Base/RCTEventDispatcher.m
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTEventDispatcher.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
|
||||
@implementation RCTEventDispatcher
|
||||
{
|
||||
RCTBridge __weak *_bridge;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (NSArray *)JSMethods
|
||||
{
|
||||
return @[
|
||||
@"RCTNativeAppEventEmitter.emit",
|
||||
@"RCTDeviceEventEmitter.emit",
|
||||
@"RCTEventEmitter.receiveEvent",
|
||||
];
|
||||
}
|
||||
|
||||
- (void)sendAppEventWithName:(NSString *)name body:(id)body
|
||||
{
|
||||
[_bridge enqueueJSCall:@"RCTNativeAppEventEmitter.emit"
|
||||
args:body ? @[name, body] : @[name]];
|
||||
}
|
||||
|
||||
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
|
||||
{
|
||||
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
|
||||
args:body ? @[name, body] : @[name]];
|
||||
}
|
||||
|
||||
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
|
||||
{
|
||||
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
|
||||
@"Event body dictionary must include a 'target' property containing a react tag");
|
||||
|
||||
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
|
||||
args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];
|
||||
}
|
||||
|
||||
- (void)sendTextEventWithType:(RCTTextEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
text:(NSString *)text
|
||||
{
|
||||
static NSString *events[] = {
|
||||
@"topFocus",
|
||||
@"topBlur",
|
||||
@"topChange",
|
||||
@"topSubmitEditing",
|
||||
@"topEndEditing",
|
||||
};
|
||||
|
||||
[self sendInputEventWithName:events[type] body:text ? @{
|
||||
@"text": text,
|
||||
@"target": reactTag
|
||||
} : @{
|
||||
@"target": reactTag
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: throttling
|
||||
* NOTE: the old system used a per-scrollview throttling
|
||||
* which would be fairly easy to re-implement if needed,
|
||||
* but this is non-optimal as it leads to degradation in
|
||||
* scroll responsiveness. A better solution would be to
|
||||
* coalesce multiple scroll events into a single batch.
|
||||
*/
|
||||
- (void)sendScrollEventWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData
|
||||
{
|
||||
static NSString *events[] = {
|
||||
@"topScrollBeginDrag",
|
||||
@"topScroll",
|
||||
@"topScrollEndDrag",
|
||||
@"topMomentumScrollBegin",
|
||||
@"topMomentumScrollEnd",
|
||||
@"topScrollAnimationEnd",
|
||||
};
|
||||
|
||||
NSDictionary *body = @{
|
||||
@"contentOffset": @{
|
||||
@"x": @(scrollView.contentOffset.x),
|
||||
@"y": @(scrollView.contentOffset.y)
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @(scrollView.contentSize.width),
|
||||
@"height": @(scrollView.contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @(scrollView.frame.size.width),
|
||||
@"height": @(scrollView.frame.size.height)
|
||||
},
|
||||
@"zoomScale": @(scrollView.zoomScale ?: 1),
|
||||
@"target": reactTag
|
||||
};
|
||||
|
||||
if (userData) {
|
||||
NSMutableDictionary *mutableBody = [body mutableCopy];
|
||||
[mutableBody addEntriesFromDictionary:userData];
|
||||
body = mutableBody;
|
||||
}
|
||||
|
||||
[self sendInputEventWithName:events[type] body:body];
|
||||
}
|
||||
|
||||
@end
|
||||
23
React/Base/RCTInvalidating.h
Normal file
23
React/Base/RCTInvalidating.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// TODO (#5906496): is there a reason for this protocol? It seems to be
|
||||
// used in a number of places where it isn't really required - only the
|
||||
// RCTBridge actually ever calls casts to it - in all other
|
||||
// cases it is simply a way of adding some method definitions to classes
|
||||
|
||||
@protocol RCTInvalidating <NSObject>
|
||||
|
||||
@property (nonatomic, assign, readonly, getter = isValid) BOOL valid;
|
||||
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
31
React/Base/RCTJSMethodRegistrar.h
Normal file
31
React/Base/RCTJSMethodRegistrar.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
/**
|
||||
* Provides an interface to register JS methods to be called via the bridge.
|
||||
*/
|
||||
@protocol RCTJSMethodRegistrar <NSObject>
|
||||
@optional
|
||||
|
||||
/**
|
||||
* An array of JavaScript methods that the class will call via the
|
||||
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
|
||||
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a
|
||||
* method that has not been registered will result in an error. If a method
|
||||
* has already been registered by another class, it is not necessary to
|
||||
* register it again, but it is good practice. Registering the same method
|
||||
* more than once is silently ignored and will not result in an error.
|
||||
*/
|
||||
+ (NSArray *)JSMethods;
|
||||
|
||||
@end
|
||||
42
React/Base/RCTJavaScriptExecutor.h
Normal file
42
React/Base/RCTJavaScriptExecutor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
|
||||
typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
||||
|
||||
/**
|
||||
* Abstracts away a JavaScript execution context - we may be running code in a
|
||||
* web view (for debugging purposes), or may be running code in a `JSContext`.
|
||||
*/
|
||||
@protocol RCTJavaScriptExecutor <RCTInvalidating>
|
||||
|
||||
/**
|
||||
* Executes given method with arguments on JS thread and calls the given callback
|
||||
* with JSValue and JSContext as a result of the JS module call.
|
||||
*/
|
||||
- (void)executeJSCall:(NSString *)name
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)arguments
|
||||
callback:(RCTJavaScriptCallback)onComplete;
|
||||
|
||||
/**
|
||||
* Runs an application script, and notifies of the script load being complete via `onComplete`.
|
||||
*/
|
||||
- (void)executeApplicationScript:(NSString *)script
|
||||
sourceURL:(NSURL *)url
|
||||
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
- (void)injectJSONText:(NSString *)script
|
||||
asGlobalObjectNamed:(NSString *)objectName
|
||||
callback:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
@end
|
||||
35
React/Base/RCTKeyCommands.h
Normal file
35
React/Base/RCTKeyCommands.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTKeyCommands : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Register a keyboard command.
|
||||
*/
|
||||
- (void)registerKeyCommandWithInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags
|
||||
action:(void (^)(UIKeyCommand *command))block;
|
||||
|
||||
/**
|
||||
* Unregister a keyboard command.
|
||||
*/
|
||||
- (void)unregisterKeyCommandWithInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags;
|
||||
|
||||
/**
|
||||
* Check if a command is registered.
|
||||
*/
|
||||
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags;
|
||||
|
||||
@end
|
||||
123
React/Base/RCTKeyCommands.m
Normal file
123
React/Base/RCTKeyCommands.m
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTKeyCommands.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTKeyCommands ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary *commandBindings;
|
||||
|
||||
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key;
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIApplication (RCTKeyCommands)
|
||||
|
||||
- (NSArray *)RCT_keyCommands
|
||||
{
|
||||
NSDictionary *commandBindings = [RCTKeyCommands sharedInstance].commandBindings;
|
||||
return [[self RCT_keyCommands] arrayByAddingObjectsFromArray:[commandBindings allKeys]];
|
||||
}
|
||||
|
||||
- (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
|
||||
{
|
||||
if (action == @selector(RCT_handleKeyCommand:)) {
|
||||
[[RCTKeyCommands sharedInstance] RCT_handleKeyCommand:sender];
|
||||
return YES;
|
||||
}
|
||||
return [self RCT_sendAction:action to:target from:sender forEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTKeyCommands
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
//swizzle UIApplication
|
||||
RCTSwapInstanceMethods([UIApplication class], @selector(keyCommands), @selector(RCT_keyCommands));
|
||||
RCTSwapInstanceMethods([UIApplication class], @selector(sendAction:to:from:forEvent:), @selector(RCT_sendAction:to:from:forEvent:));
|
||||
}
|
||||
|
||||
static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static RCTKeyCommands *sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_commandBindings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key
|
||||
{
|
||||
// NOTE: We should just be able to do commandBindings[key] here, but curiously, the
|
||||
// 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) {
|
||||
if ([key.input isEqualToString:k.input] && key.modifierFlags == k.modifierFlags) {
|
||||
block(key);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)registerKeyCommandWithInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags
|
||||
action:(void (^)(UIKeyCommand *))block
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
||||
modifierFlags:flags
|
||||
action:@selector(RCT_handleKeyCommand:)];
|
||||
_commandBindings[command] = block;
|
||||
}
|
||||
|
||||
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
for (UIKeyCommand *key in [_commandBindings allKeys]) {
|
||||
if ([key.input isEqualToString:input] && key.modifierFlags == flags) {
|
||||
[_commandBindings removeObjectForKey:key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
for (UIKeyCommand *key in [_commandBindings allKeys]) {
|
||||
if ([key.input isEqualToString:input] && key.modifierFlags == flags) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
65
React/Base/RCTLog.h
Normal file
65
React/Base/RCTLog.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#define RCTLOG_INFO 1
|
||||
#define RCTLOG_WARN 2
|
||||
#define RCTLOG_ERROR 3
|
||||
#define RCTLOG_MUSTFIX 4
|
||||
|
||||
// If set to e.g. `RCTLOG_ERROR`, will assert after logging the first error.
|
||||
#if DEBUG
|
||||
#define RCTLOG_FATAL_LEVEL RCTLOG_MUSTFIX
|
||||
#define RCTLOG_REDBOX_LEVEL RCTLOG_ERROR
|
||||
#else
|
||||
#define RCTLOG_FATAL_LEVEL (RCTLOG_MUSTFIX + 1)
|
||||
#define RCTLOG_REDBOX_LEVEL (RCTLOG_MUSTFIX + 1)
|
||||
#endif
|
||||
|
||||
// If defined, only log messages that match this regex will fatal
|
||||
#define RCTLOG_FATAL_REGEX nil
|
||||
|
||||
extern __unsafe_unretained NSString *RCTLogLevels[];
|
||||
|
||||
#define _RCTLog(_level, ...) do { \
|
||||
NSString *__RCTLog__levelStr = RCTLogLevels[_level - 1]; \
|
||||
NSString *__RCTLog__msg = RCTLogObjects(RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__), __RCTLog__levelStr); \
|
||||
if (_level >= RCTLOG_FATAL_LEVEL) { \
|
||||
BOOL __RCTLog__fail = YES; \
|
||||
if (RCTLOG_FATAL_REGEX) { \
|
||||
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:NULL]; \
|
||||
__RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \
|
||||
} \
|
||||
RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \
|
||||
} \
|
||||
if (_level >= RCTLOG_REDBOX_LEVEL) { \
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:__RCTLog__msg]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
|
||||
#define RCTLogInfo(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
|
||||
#define RCTLogWarn(...) _RCTLog(RCTLOG_WARN, __VA_ARGS__)
|
||||
#define RCTLogError(...) _RCTLog(RCTLOG_ERROR, __VA_ARGS__)
|
||||
#define RCTLogMustFix(...) _RCTLog(RCTLOG_MUSTFIX, __VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
NSString *RCTLogObjects(NSArray *objects, NSString *level);
|
||||
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
|
||||
|
||||
void RCTInjectLogFunction(void (^logFunction)(NSString *msg));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
93
React/Base/RCTLog.m
Normal file
93
React/Base/RCTLog.m
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
__unsafe_unretained NSString *RCTLogLevels[] = {
|
||||
@"info",
|
||||
@"warn",
|
||||
@"error",
|
||||
@"mustfix"
|
||||
};
|
||||
|
||||
static void (^RCTInjectedLogFunction)(NSString *msg);
|
||||
|
||||
void RCTInjectLogFunction(void (^logFunction)(NSString *msg)) {
|
||||
RCTInjectedLogFunction = logFunction;
|
||||
}
|
||||
|
||||
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName)
|
||||
{
|
||||
NSString *threadName = [[NSThread currentThread] name];
|
||||
NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent];
|
||||
if (!threadName || threadName.length <= 0) {
|
||||
threadName = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"[RCTLog][tid:%@][%@:%d]>", threadName, fileName, lineNumber];
|
||||
}
|
||||
|
||||
// TODO (#5906496): // kinda ugly that this is tied to RCTBridge
|
||||
NSString *RCTLogObjects(NSArray *objects, NSString *level)
|
||||
{
|
||||
NSString *str = objects[0];
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
if ([RCTBridge hasValidJSExecutor]) {
|
||||
fprintf(stderr, "%s\n", [str UTF8String]); // don't print timestamps and other junk
|
||||
[RCTBridge log:objects level:level];
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Print normal errors with timestamps when not in simulator.
|
||||
// Non errors are already compiled out above, so log as error here.
|
||||
if (RCTInjectedLogFunction) {
|
||||
RCTInjectedLogFunction(str);
|
||||
} else {
|
||||
NSLog(@">\n %@", str);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// Returns array of objects. First arg is a simple string to print, remaining args
|
||||
// are objects to pass through to the debugger so they are inspectable in the console.
|
||||
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
|
||||
|
||||
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
|
||||
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
|
||||
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
|
||||
BOOL valid = YES;
|
||||
for (int i = 0; i < formatParts.count; i++) {
|
||||
if (i == 0) { // first part is always a string
|
||||
[objects addObject:formatParts[i]];
|
||||
} else {
|
||||
if (valid && [formatParts[i] length] && [formatParts[i] characterAtIndex:0] == '@') {
|
||||
id obj = va_arg(args, id);
|
||||
[objects addObject:obj ?: @"null"];
|
||||
[objects addObject:[formatParts[i] substringFromIndex:1]]; // remove formatting char
|
||||
} else {
|
||||
// We could determine the type (double, int?) of the va_arg by parsing the formatPart, but for now we just bail.
|
||||
valid = NO;
|
||||
[objects addObject:[NSString stringWithFormat:@"unknown object for %%%@", formatParts[i]]];
|
||||
}
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
va_start(args, format);
|
||||
NSString *strOut = [preamble stringByAppendingString:[[NSString alloc] initWithFormat:format arguments:args]];
|
||||
va_end(args);
|
||||
NSMutableArray *objectsOut = [NSMutableArray arrayWithObject:strOut];
|
||||
[objectsOut addObjectsFromArray:objects];
|
||||
return objectsOut;
|
||||
}
|
||||
25
React/Base/RCTRedBox.h
Normal file
25
React/Base/RCTRedBox.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTRedBox : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message;
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details;
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack;
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack;
|
||||
|
||||
- (NSString *)currentErrorMessage;
|
||||
|
||||
- (void)dismiss;
|
||||
|
||||
@end
|
||||
316
React/Base/RCTRedBox.m
Normal file
316
React/Base/RCTRedBox.m
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@property (nonatomic, copy) NSString *lastErrorMessage;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRedBoxWindow
|
||||
{
|
||||
UIView *_rootView;
|
||||
UITableView *_stackTraceTableView;
|
||||
|
||||
NSArray *_lastStackTrace;
|
||||
|
||||
UITableViewCell *_cachedMessageCell;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
|
||||
self.windowLevel = UIWindowLevelStatusBar + 5;
|
||||
self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
|
||||
self.hidden = YES;
|
||||
|
||||
UIViewController *rootController = [[UIViewController alloc] init];
|
||||
self.rootViewController = rootController;
|
||||
_rootView = rootController.view;
|
||||
_rootView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
const CGFloat buttonHeight = 60;
|
||||
|
||||
CGRect detailsFrame = _rootView.bounds;
|
||||
detailsFrame.size.height -= buttonHeight;
|
||||
|
||||
_stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
|
||||
_stackTraceTableView.delegate = self;
|
||||
_stackTraceTableView.dataSource = self;
|
||||
_stackTraceTableView.backgroundColor = [UIColor clearColor];
|
||||
_stackTraceTableView.separatorColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3];
|
||||
_stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
[_rootView addSubview:_stackTraceTableView];
|
||||
|
||||
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
dismissButton.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
[dismissButton setTitle:@"Dismiss (ESC)" forState:UIControlStateNormal];
|
||||
[dismissButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
|
||||
[dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
|
||||
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
reloadButton.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
[reloadButton setTitle:@"Reload JS (\u2318R)" forState:UIControlStateNormal];
|
||||
[reloadButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
|
||||
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
|
||||
[reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
CGFloat buttonWidth = self.bounds.size.width / 2;
|
||||
dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
[_rootView addSubview:dismissButton];
|
||||
[_rootView addSubview:reloadButton];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)openStackFrameInEditor:(NSDictionary *)stackFrame
|
||||
{
|
||||
NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[stackFrameJSON length]];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
|
||||
request.URL = [NSURL URLWithString:@"http://localhost:8081/open-stack-frame"];
|
||||
request.HTTPMethod = @"POST";
|
||||
request.HTTPBody = stackFrameJSON;
|
||||
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) {
|
||||
_lastStackTrace = stack;
|
||||
_lastErrorMessage = message;
|
||||
|
||||
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
|
||||
[_stackTraceTableView reloadData];
|
||||
[_stackTraceTableView setNeedsLayout];
|
||||
|
||||
if (self.hidden) {
|
||||
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
|
||||
atScrollPosition:UITableViewScrollPositionTop
|
||||
animated:NO];
|
||||
}
|
||||
|
||||
[self makeKeyAndVisible];
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
self.hidden = YES;
|
||||
[self resignFirstResponder];
|
||||
[[[[UIApplication sharedApplication] delegate] window] makeKeyWindow];
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil];
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
#pragma mark - TableView
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return section == 0 ? 1 : [_lastStackTrace count];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 0) {
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
|
||||
return [self reuseCell:cell forErrorMessage:_lastErrorMessage];
|
||||
}
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
|
||||
NSUInteger index = [indexPath row];
|
||||
NSDictionary *stackFrame = _lastStackTrace[index];
|
||||
return [self reuseCell:cell forStackFrame:stackFrame];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message
|
||||
{
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"];
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
cell.textLabel.numberOfLines = 0;
|
||||
cell.detailTextLabel.textColor = [UIColor whiteColor];
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
cell.textLabel.text = message;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(NSDictionary *)stackFrame
|
||||
{
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
|
||||
cell.textLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9];
|
||||
cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
|
||||
cell.detailTextLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
|
||||
cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11];
|
||||
cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.selectedBackgroundView = [[UIView alloc] init];
|
||||
cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2];
|
||||
}
|
||||
|
||||
cell.textLabel.text = stackFrame[@"methodName"];
|
||||
cell.detailTextLabel.text = cell.detailTextLabel.text = [NSString stringWithFormat:@"%@:%@", [stackFrame[@"file"] lastPathComponent], stackFrame[@"lineNumber"]];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 0) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
|
||||
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16],
|
||||
NSParagraphStyleAttributeName: paragraphStyle};
|
||||
CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
|
||||
return ceil(boundingRect.size.height) + 40;
|
||||
} else {
|
||||
return 44;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 1) {
|
||||
NSUInteger row = [indexPath row];
|
||||
NSDictionary *stackFrame = _lastStackTrace[row];
|
||||
[self openStackFrameInEditor:stackFrame];
|
||||
}
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Key commands
|
||||
|
||||
- (NSArray *)keyCommands
|
||||
{
|
||||
// NOTE: We could use RCTKeyCommands for this, but since
|
||||
// we control this window, we can use the standard, non-hacky
|
||||
// mechanism instead
|
||||
|
||||
return @[
|
||||
|
||||
// Dismiss red box
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
|
||||
modifierFlags:0
|
||||
action:@selector(dismiss)],
|
||||
|
||||
// Reload
|
||||
[UIKeyCommand keyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:@selector(reload)]
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRedBox
|
||||
{
|
||||
RCTRedBoxWindow *_window;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static RCTRedBox *_sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_sharedInstance = [[RCTRedBox alloc] init];
|
||||
});
|
||||
return _sharedInstance;
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message
|
||||
{
|
||||
[self showErrorMessage:message withStack:nil showIfHidden:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
|
||||
{
|
||||
NSString *combinedMessage = message;
|
||||
if (details) {
|
||||
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
|
||||
}
|
||||
[self showErrorMessage:combinedMessage];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:YES];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
if (!_window) {
|
||||
_window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
}
|
||||
[_window showErrorMessage:message withStack:stack showIfHidden:shouldShow];
|
||||
};
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)currentErrorMessage
|
||||
{
|
||||
if (_window && !_window.hidden) {
|
||||
return _window.lastErrorMessage;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
[_window dismiss];
|
||||
}
|
||||
|
||||
@end
|
||||
70
React/Base/RCTRootView.h
Normal file
70
React/Base/RCTRootView.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
@interface RCTRootView : UIView<RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions /* NS_DESIGNATED_INITIALIZER */;
|
||||
|
||||
/**
|
||||
* The URL of the bundled application script (required).
|
||||
* Setting this will clear the view contents, and trigger
|
||||
* an asynchronous load/download and execution of the script.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSURL *scriptURL;
|
||||
|
||||
/**
|
||||
* The name of the JavaScript module to execute within the
|
||||
* specified scriptURL (required). Setting this will not have
|
||||
* any immediate effect, but it must be done prior to loading
|
||||
* the script.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *moduleName;
|
||||
|
||||
/**
|
||||
* A block that returns an array of pre-allocated modules. These
|
||||
* modules will take precedence over any automatically registered
|
||||
* modules of the same name.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
|
||||
|
||||
/**
|
||||
* The default properties to apply to the view when the script bundle
|
||||
* is first loaded. Defaults to nil/empty.
|
||||
*/
|
||||
@property (nonatomic, copy) NSDictionary *initialProperties;
|
||||
|
||||
/**
|
||||
* The class of the RCTJavaScriptExecutor to use with this view.
|
||||
* If not specified, it will default to using RCTContextExecutor.
|
||||
* Changes will take effect next time the bundle is reloaded.
|
||||
*/
|
||||
@property (nonatomic, strong) Class executorClass;
|
||||
|
||||
/**
|
||||
* If YES will watch for shake gestures and show development menu
|
||||
* with options like "Reload", "Enable Debugging", etc.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableDevMenu;
|
||||
|
||||
/**
|
||||
* Reload this root view, or all root views, respectively.
|
||||
*/
|
||||
- (void)reload;
|
||||
+ (void)reloadAll;
|
||||
|
||||
- (void)startOrResetInteractionTiming;
|
||||
- (NSDictionary *)endAndResetInteractionTiming;
|
||||
|
||||
@end
|
||||
352
React/Base/RCTRootView.m
Normal file
352
React/Base/RCTRootView.m
Normal file
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTRootView.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTDevMenu.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTTouchHandler.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTWebViewExecutor.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
||||
|
||||
/**
|
||||
* HACK(t6568049) This should be removed soon, hiding to prevent people from
|
||||
* relying on it
|
||||
*/
|
||||
@interface RCTBridge (RCTRootView)
|
||||
|
||||
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRootView
|
||||
{
|
||||
RCTDevMenu *_devMenu;
|
||||
RCTBridge *_bridge;
|
||||
RCTTouchHandler *_touchHandler;
|
||||
id<RCTJavaScriptExecutor> _executor;
|
||||
BOOL _registered;
|
||||
NSDictionary *_launchOptions;
|
||||
}
|
||||
|
||||
static Class _globalExecutorClass;
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
// Register Cmd-R as a global refresh key
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
[self reloadAll];
|
||||
}];
|
||||
|
||||
// Cmd-D reloads using the web view executor, allows attaching from Safari dev tools.
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
_globalExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
if (!_globalExecutorClass) {
|
||||
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
|
||||
}
|
||||
[self reloadAll];
|
||||
}];
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
RCTAssert(bundleURL, @"A bundleURL is required to create an RCTRootView");
|
||||
RCTAssert(moduleName, @"A bundleURL is required to create an RCTRootView");
|
||||
_moduleName = moduleName;
|
||||
_launchOptions = launchOptions;
|
||||
[self setUp];
|
||||
[self setScriptURL:bundleURL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* HACK(t6568049) Private constructor for testing purposes
|
||||
*/
|
||||
- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_moduleProvider = moduleProvider;
|
||||
_moduleName = moduleName;
|
||||
_launchOptions = launchOptions;
|
||||
[self setUp];
|
||||
[self setScriptURL:bundleURL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// Every root view that is created must have a unique react tag.
|
||||
// Numbering of these tags goes from 1, 11, 21, 31, etc
|
||||
static NSInteger rootViewTag = 1;
|
||||
self.reactTag = @(rootViewTag);
|
||||
#ifdef DEBUG
|
||||
self.enableDevMenu = YES;
|
||||
#endif
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
rootViewTag += 10;
|
||||
|
||||
// Add reload observer
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
name:RCTReloadNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
||||
{
|
||||
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
|
||||
if (!_devMenu) {
|
||||
_devMenu = [[RCTDevMenu alloc] initWithRootView:self];
|
||||
}
|
||||
[_devMenu show];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSArray *)JSMethods
|
||||
{
|
||||
return @[
|
||||
@"AppRegistry.runApplication",
|
||||
@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
args:@[self.reactTag]];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return [_bridge isValid];
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
// Clear view
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
[self removeGestureRecognizer:_touchHandler];
|
||||
[_touchHandler invalidate];
|
||||
[_executor invalidate];
|
||||
|
||||
// TODO: eventually we'll want to be able to share the bridge between
|
||||
// multiple rootviews, in which case we'll need to move this elsewhere
|
||||
[_bridge invalidate];
|
||||
}
|
||||
|
||||
#pragma mark Bundle loading
|
||||
|
||||
- (void)bundleFinishedLoading:(NSError *)error
|
||||
{
|
||||
if (error != nil) {
|
||||
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
|
||||
if (stack) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
|
||||
} else {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
|
||||
}
|
||||
} else {
|
||||
|
||||
[_bridge.uiManager registerRootView:self];
|
||||
_registered = YES;
|
||||
|
||||
NSString *moduleName = _moduleName ?: @"";
|
||||
NSDictionary *appParameters = @{
|
||||
@"rootTag": self.reactTag,
|
||||
@"initialProps": self.initialProperties ?: @{},
|
||||
};
|
||||
[_bridge enqueueJSCall:@"AppRegistry.runApplication"
|
||||
args:@[moduleName, appParameters]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadBundle
|
||||
{
|
||||
[self invalidate];
|
||||
|
||||
if (!_scriptURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
[self removeGestureRecognizer:_touchHandler];
|
||||
[_touchHandler invalidate];
|
||||
[_executor invalidate];
|
||||
[_bridge invalidate];
|
||||
|
||||
_registered = NO;
|
||||
|
||||
// Choose local executor if specified, followed by global, followed by default
|
||||
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
|
||||
|
||||
/**
|
||||
* HACK(t6568049) Most of the properties passed into the bridge are not used
|
||||
* right now but it'll be changed soon so it's here for convenience.
|
||||
*/
|
||||
_bridge = [[RCTBridge alloc] initWithBundlePath:_scriptURL.absoluteString
|
||||
moduleProvider:_moduleProvider
|
||||
launchOptions:_launchOptions];
|
||||
[_bridge setJavaScriptExecutor:_executor];
|
||||
|
||||
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
||||
[self addGestureRecognizer:_touchHandler];
|
||||
|
||||
// Load the bundle
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:_scriptURL completionHandler:
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from React root",
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
[self bundleFinishedLoading:error];
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response as text
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": @[@{
|
||||
@"methodName": errorDetails[@"description"] ?: @"",
|
||||
@"file": errorDetails[@"filename"] ?: @"",
|
||||
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
|
||||
[self bundleFinishedLoading:error];
|
||||
return;
|
||||
}
|
||||
if (!_bridge.isValid) {
|
||||
return; // Bridge was invalidated in the meanwhile
|
||||
}
|
||||
|
||||
// Success!
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = _scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *_error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_bridge.isValid) {
|
||||
[self bundleFinishedLoading:_error];
|
||||
}
|
||||
});
|
||||
}];
|
||||
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
- (void)setScriptURL:(NSURL *)scriptURL
|
||||
{
|
||||
if ([_scriptURL isEqual:scriptURL]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scriptURL = scriptURL;
|
||||
[self loadBundle];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
if (_registered) {
|
||||
[_bridge.uiManager setFrame:self.frame forRootView:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[self loadBundle];
|
||||
}
|
||||
|
||||
+ (void)reloadAll
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)startOrResetInteractionTiming
|
||||
{
|
||||
[_touchHandler startOrResetInteractionTiming];
|
||||
}
|
||||
|
||||
- (NSDictionary *)endAndResetInteractionTiming
|
||||
{
|
||||
return [_touchHandler endAndResetInteractionTiming];
|
||||
}
|
||||
|
||||
@end
|
||||
38
React/Base/RCTSparseArray.h
Normal file
38
React/Base/RCTSparseArray.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface RCTSparseArray : NSObject <NSCopying>
|
||||
|
||||
- (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
+ (instancetype)sparseArray;
|
||||
+ (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity;
|
||||
+ (instancetype)sparseArrayWithSparseArray:(RCTSparseArray *)sparseArray;
|
||||
|
||||
// Use nil object to remove at idx.
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
|
||||
|
||||
// Use nil obj to remove at key.
|
||||
- (void)setObject:(id)obj forKeyedSubscript:(NSNumber *)key;
|
||||
- (id)objectForKeyedSubscript:(NSNumber *)key;
|
||||
|
||||
@property (readonly, nonatomic) NSUInteger count;
|
||||
@property (readonly, nonatomic, copy) NSArray *allIndexes;
|
||||
@property (readonly, nonatomic, copy) NSArray *allObjects;
|
||||
|
||||
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block;
|
||||
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block;
|
||||
|
||||
- (void)removeAllObjects;
|
||||
|
||||
@end
|
||||
123
React/Base/RCTSparseArray.m
Normal file
123
React/Base/RCTSparseArray.m
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTSparseArray.h"
|
||||
|
||||
@implementation RCTSparseArray
|
||||
{
|
||||
NSMutableDictionary *_storage;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithCapacity:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCapacity:(NSUInteger)capacity
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_storage = [NSMutableDictionary dictionaryWithCapacity:capacity];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_storage = [sparseArray->_storage copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)sparseArray
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
+ (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity
|
||||
{
|
||||
return [[self alloc] initWithCapacity:capacity];
|
||||
}
|
||||
|
||||
+ (instancetype)sparseArrayWithSparseArray:(RCTSparseArray *)sparseArray
|
||||
{
|
||||
return [[self alloc] initWithSparseArray:sparseArray];
|
||||
}
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)idx
|
||||
{
|
||||
return _storage[@(idx)];
|
||||
}
|
||||
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
|
||||
{
|
||||
self[@(idx)] = obj;
|
||||
}
|
||||
|
||||
- (id)objectForKeyedSubscript:(NSNumber *)key
|
||||
{
|
||||
return _storage[key];
|
||||
}
|
||||
|
||||
- (void)setObject:(id)obj forKeyedSubscript:(NSNumber *)key
|
||||
{
|
||||
if (obj) {
|
||||
_storage[key] = obj;
|
||||
} else {
|
||||
[_storage removeObjectForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)count
|
||||
{
|
||||
return _storage.count;
|
||||
}
|
||||
|
||||
- (NSArray *)allIndexes
|
||||
{
|
||||
return _storage.allKeys;
|
||||
}
|
||||
|
||||
- (NSArray *)allObjects
|
||||
{
|
||||
return _storage.allValues;
|
||||
}
|
||||
|
||||
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block
|
||||
{
|
||||
NSParameterAssert(block != nil);
|
||||
[_storage enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
|
||||
block(obj, key, stop);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block
|
||||
{
|
||||
NSParameterAssert(block != nil);
|
||||
[_storage enumerateKeysAndObjectsWithOptions:opts usingBlock:^(NSNumber *key, id obj, BOOL *stop) {
|
||||
block(obj, key, stop);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeAllObjects
|
||||
{
|
||||
[_storage removeAllObjects];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [[[self class] allocWithZone:zone] initWithSparseArray:self];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [[super description] stringByAppendingString:[_storage description]];
|
||||
}
|
||||
|
||||
@end
|
||||
22
React/Base/RCTTouchHandler.h
Normal file
22
React/Base/RCTTouchHandler.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTTouchHandler : UIGestureRecognizer<RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
- (void)startOrResetInteractionTiming;
|
||||
- (NSDictionary *)endAndResetInteractionTiming;
|
||||
|
||||
@end
|
||||
358
React/Base/RCTTouchHandler.m
Normal file
358
React/Base/RCTTouchHandler.m
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTTouchHandler.h"
|
||||
|
||||
#import <UIKit/UIGestureRecognizerSubclass.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
// TODO: this class behaves a lot like a module, and could be implemented as a
|
||||
// module if we were to assume that modules and RootViews had a 1:1 relationship
|
||||
|
||||
@interface RCTTouchEvent : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger id;
|
||||
@property (nonatomic, copy, readonly) NSString *eventName;
|
||||
@property (nonatomic, copy, readonly) NSArray *touches;
|
||||
@property (nonatomic, copy, readonly) NSArray *changedIndexes;
|
||||
@property (nonatomic, assign, readonly) CFTimeInterval originatingTime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation RCTTouchEvent
|
||||
|
||||
+ (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime
|
||||
{
|
||||
RCTTouchEvent *touchEvent = [[self alloc] init];
|
||||
touchEvent->_id = [self newID];
|
||||
touchEvent->_eventName = [eventName copy];
|
||||
touchEvent->_touches = [touches copy];
|
||||
touchEvent->_changedIndexes = [changedIndexes copy];
|
||||
touchEvent->_originatingTime = originatingTime;
|
||||
return touchEvent;
|
||||
}
|
||||
|
||||
+ (NSUInteger)newID
|
||||
{
|
||||
static NSUInteger id = 0;
|
||||
return ++id;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTouchHandler
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
|
||||
/**
|
||||
* Arrays managed in parallel tracking native touch object along with the
|
||||
* native view that was touched, and the react touch data dictionary.
|
||||
* This must be kept track of because `UIKit` destroys the touch targets
|
||||
* if touches are canceled and we have no other way to recover this information.
|
||||
*/
|
||||
NSMutableOrderedSet *_nativeTouches;
|
||||
NSMutableArray *_reactTouches;
|
||||
NSMutableArray *_touchViews;
|
||||
|
||||
BOOL _recordingInteractionTiming;
|
||||
CFTimeInterval _mostRecentEnqueueJS;
|
||||
CADisplayLink *_displayLink;
|
||||
NSMutableArray *_pendingTouches;
|
||||
NSMutableArray *_bridgeInteractionTiming;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super initWithTarget:nil action:NULL])) {
|
||||
|
||||
RCTAssert(bridge != nil, @"Expect an event dispatcher");
|
||||
|
||||
_bridge = bridge;
|
||||
|
||||
_nativeTouches = [[NSMutableOrderedSet alloc] init];
|
||||
_reactTouches = [[NSMutableArray alloc] init];
|
||||
_touchViews = [[NSMutableArray alloc] init];
|
||||
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
|
||||
_pendingTouches = [[NSMutableArray alloc] init];
|
||||
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
||||
|
||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
|
||||
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
|
||||
// level components not build using RCT, will fail to recognize gestures.
|
||||
self.cancelsTouchesInView = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _displayLink != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
RCTTouchEventTypeStart,
|
||||
RCTTouchEventTypeMove,
|
||||
RCTTouchEventTypeEnd,
|
||||
RCTTouchEventTypeCancel
|
||||
};
|
||||
|
||||
#pragma mark - Bookkeeping for touch indices
|
||||
|
||||
- (void)_recordNewTouches:(NSSet *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
|
||||
RCTAssert(![_nativeTouches containsObject:touch],
|
||||
@"Touch is already recorded. This is a critical bug.");
|
||||
|
||||
// Find closest React-managed touchable view
|
||||
UIView *targetView = touch.view;
|
||||
while (targetView) {
|
||||
if (targetView.reactTag && targetView.userInteractionEnabled &&
|
||||
[targetView reactRespondsToTouch:touch]) {
|
||||
break;
|
||||
}
|
||||
targetView = targetView.superview;
|
||||
}
|
||||
|
||||
NSNumber *reactTag = [targetView reactTagAtPoint:[touch locationInView:targetView]];
|
||||
if (!reactTag || !targetView.userInteractionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get new, unique touch id
|
||||
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
|
||||
NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches;
|
||||
for (NSDictionary *reactTouch in _reactTouches) {
|
||||
NSInteger usedID = [reactTouch[@"target"] integerValue];
|
||||
if (usedID == touchID) {
|
||||
// ID has already been used, try next value
|
||||
touchID ++;
|
||||
} else if (usedID > touchID) {
|
||||
// If usedID > touchID, touchID must be unique, so we can stop looking
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create touch
|
||||
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:9];
|
||||
reactTouch[@"target"] = reactTag;
|
||||
reactTouch[@"identifier"] = @(touchID);
|
||||
reactTouch[@"touches"] = [NSNull null]; // We hijack this touchObj to serve both as an event
|
||||
reactTouch[@"changedTouches"] = [NSNull null]; // and as a Touch object, so making this JIT friendly.
|
||||
|
||||
// Add to arrays
|
||||
[_touchViews addObject:targetView];
|
||||
[_nativeTouches addObject:touch];
|
||||
[_reactTouches addObject:reactTouch];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_recordRemovedTouches:(NSSet *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
NSUInteger index = [_nativeTouches indexOfObject:touch];
|
||||
if(index == NSNotFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[_touchViews removeObjectAtIndex:index];
|
||||
[_nativeTouches removeObjectAtIndex:index];
|
||||
[_reactTouches removeObjectAtIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
|
||||
{
|
||||
UITouch *nativeTouch = _nativeTouches[touchIndex];
|
||||
CGPoint windowLocation = [nativeTouch locationInView:nativeTouch.window];
|
||||
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:self.view];
|
||||
|
||||
UIView *touchView = _touchViews[touchIndex];
|
||||
CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView];
|
||||
|
||||
NSMutableDictionary *reactTouch = _reactTouches[touchIndex];
|
||||
reactTouch[@"pageX"] = @(rootViewLocation.x);
|
||||
reactTouch[@"pageY"] = @(rootViewLocation.y);
|
||||
reactTouch[@"locationX"] = @(touchViewLocation.x);
|
||||
reactTouch[@"locationY"] = @(touchViewLocation.y);
|
||||
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
|
||||
}
|
||||
|
||||
+ (NSArray *)JSMethods
|
||||
{
|
||||
return @[@"RCTEventEmitter.receiveTouches"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs information about touch events to send across the serialized
|
||||
* boundary. This data should be compliant with W3C `Touch` objects. This data
|
||||
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
|
||||
* there must be a simple receiver on the other side of the bridge that
|
||||
* organizes the touch objects into `Event`s.
|
||||
*
|
||||
* We send the data as an array of `Touch`es, the type of action
|
||||
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
|
||||
* from that array.
|
||||
*/
|
||||
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
|
||||
{
|
||||
// Update touches
|
||||
CFTimeInterval enqueueTime = CACurrentMediaTime();
|
||||
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
|
||||
for (UITouch *touch in touches) {
|
||||
NSInteger index = [_nativeTouches indexOfObject:touch];
|
||||
if (index == NSNotFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[self _updateReactTouchAtIndex:index];
|
||||
[changedIndexes addObject:@(index)];
|
||||
}
|
||||
|
||||
if (changedIndexes.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deep copy the touches because they will be accessed from another thread
|
||||
// TODO: would it be safer to do this in the bridge or executor, rather than trusting caller?
|
||||
NSMutableArray *reactTouches = [[NSMutableArray alloc] initWithCapacity:_reactTouches.count];
|
||||
for (NSDictionary *touch in _reactTouches) {
|
||||
[reactTouches addObject:[touch copy]];
|
||||
}
|
||||
|
||||
RCTTouchEvent *touch = [RCTTouchEvent touchWithEventName:eventName
|
||||
touches:reactTouches
|
||||
changedIndexes:changedIndexes
|
||||
originatingTime:originatingTime];
|
||||
[_pendingTouches addObject:touch];
|
||||
|
||||
if (_recordingInteractionTiming) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(touch.originatingTime),
|
||||
@"operation": @"taskOriginated",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(enqueueTime),
|
||||
@"operation": @"taskEnqueuedPending",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_update:(CADisplayLink *)sender
|
||||
{
|
||||
// Dispatch touch event
|
||||
NSUInteger pendingCount = _pendingTouches.count;
|
||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
||||
_mostRecentEnqueueJS = CACurrentMediaTime();
|
||||
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
|
||||
args:@[touch.eventName, touch.touches, touch.changedIndexes]];
|
||||
}
|
||||
|
||||
if (_recordingInteractionTiming) {
|
||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"frameAlignedDispatch",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
}
|
||||
|
||||
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"mainThreadDisplayLink",
|
||||
@"taskID": @([RCTTouchEvent newID]),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
[_pendingTouches removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)startOrResetInteractionTiming
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
[_bridgeInteractionTiming removeAllObjects];
|
||||
_recordingInteractionTiming = YES;
|
||||
}
|
||||
|
||||
- (NSDictionary *)endAndResetInteractionTiming
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
_recordingInteractionTiming = NO;
|
||||
NSArray *_prevInteractionTimingData = _bridgeInteractionTiming;
|
||||
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
||||
return @{ @"interactionTiming": _prevInteractionTimingData };
|
||||
}
|
||||
|
||||
#pragma mark - Gesture Recognizer Delegate Callbacks
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
self.state = UIGestureRecognizerStateBegan;
|
||||
|
||||
// "start" has to record new touches before extracting the event.
|
||||
// "end"/"cancel" needs to remove the touch *after* extracting the event.
|
||||
[self _recordNewTouches:touches];
|
||||
[self _updateAndDispatchTouches:touches eventName:@"topTouchStart" originatingTime:event.timestamp];
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
if (self.state == UIGestureRecognizerStateFailed) {
|
||||
return;
|
||||
}
|
||||
[self _updateAndDispatchTouches:touches eventName:@"topTouchMove" originatingTime:event.timestamp];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
[self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp];
|
||||
[self _recordRemovedTouches:touches];
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesCancelled:touches withEvent:event];
|
||||
[self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp];
|
||||
[self _recordRemovedTouches:touches];
|
||||
}
|
||||
|
||||
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
58
React/Base/RCTUtils.h
Normal file
58
React/Base/RCTUtils.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <tgmath.h>
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Utility functions for JSON object <-> string serialization/deserialization
|
||||
NSString *RCTJSONStringify(id jsonObject, NSError **error);
|
||||
id RCTJSONParse(NSString *jsonString, NSError **error);
|
||||
|
||||
// Get MD5 hash of a string (TODO: currently unused. Remove?)
|
||||
NSString *RCTMD5Hash(NSString *string);
|
||||
|
||||
// Get screen metrics in a thread-safe way
|
||||
CGFloat RCTScreenScale(void);
|
||||
CGSize RCTScreenSize(void);
|
||||
|
||||
// Round float coordinates to nearest whole screen pixel (not point)
|
||||
CGFloat RCTRoundPixelValue(CGFloat value);
|
||||
CGFloat RCTCeilPixelValue(CGFloat value);
|
||||
CGFloat RCTFloorPixelValue(CGFloat value);
|
||||
|
||||
// Get current time, for precise performance metrics
|
||||
NSTimeInterval RCTTGetAbsoluteTime(void);
|
||||
|
||||
// Method swizzling
|
||||
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
|
||||
void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
|
||||
|
||||
// Module subclass support
|
||||
BOOL RCTClassOverridesClassMethod(Class cls, SEL selector);
|
||||
BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
|
||||
|
||||
// Enumerate all classes that conform to NSObject protocol
|
||||
void RCTEnumerateClasses(void (^block)(Class cls));
|
||||
|
||||
// Creates a standardized error object
|
||||
// TODO(#6472857): create NSErrors and automatically convert them over the bridge.
|
||||
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData);
|
||||
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
228
React/Base/RCTUtils.m
Normal file
228
React/Base/RCTUtils.m
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#import <mach/mach_time.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
NSString *RCTJSONStringify(id jsonObject, NSError **error)
|
||||
{
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:error];
|
||||
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
|
||||
}
|
||||
|
||||
id RCTJSONParse(NSString *jsonString, NSError **error)
|
||||
{
|
||||
if (!jsonString) {
|
||||
return nil;
|
||||
}
|
||||
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
|
||||
if (!jsonData) {
|
||||
jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
|
||||
if (jsonData) {
|
||||
RCTLogWarn(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
|
||||
} else {
|
||||
// If our backup conversion fails, log the issue so we can see what strings are causing this (t6452813)
|
||||
RCTLogError(@"RCTJSONParse received the following string, which could not be converted to UTF8 data: '%@'", jsonString);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
|
||||
}
|
||||
|
||||
NSString *RCTMD5Hash(NSString *string)
|
||||
{
|
||||
const char *str = [string UTF8String];
|
||||
unsigned char result[CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5(str, (CC_LONG)strlen(str), result);
|
||||
|
||||
return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
result[0], result[1], result[2], result[3],
|
||||
result[4], result[5], result[6], result[7],
|
||||
result[8], result[9], result[10], result[11],
|
||||
result[12], result[13], result[14], result[15]
|
||||
];
|
||||
}
|
||||
|
||||
CGFloat RCTScreenScale()
|
||||
{
|
||||
static CGFloat scale;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
});
|
||||
} else {
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
}
|
||||
});
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
CGSize RCTScreenSize()
|
||||
{
|
||||
static CGSize size;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
size = [UIScreen mainScreen].bounds.size;
|
||||
});
|
||||
} else {
|
||||
size = [UIScreen mainScreen].bounds.size;
|
||||
}
|
||||
});
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
CGFloat RCTRoundPixelValue(CGFloat value)
|
||||
{
|
||||
CGFloat scale = RCTScreenScale();
|
||||
return round(value * scale) / scale;
|
||||
}
|
||||
|
||||
CGFloat RCTCeilPixelValue(CGFloat value)
|
||||
{
|
||||
CGFloat scale = RCTScreenScale();
|
||||
return ceil(value * scale) / scale;
|
||||
}
|
||||
|
||||
CGFloat RCTFloorPixelValue(CGFloat value)
|
||||
{
|
||||
CGFloat scale = RCTScreenScale();
|
||||
return floor(value * scale) / scale;
|
||||
}
|
||||
|
||||
NSTimeInterval RCTTGetAbsoluteTime(void)
|
||||
{
|
||||
static struct mach_timebase_info tb_info = {0};
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
int ret = mach_timebase_info(&tb_info);
|
||||
assert(0 == ret);
|
||||
});
|
||||
|
||||
uint64_t timeInNanoseconds = (mach_absolute_time() * tb_info.numer) / tb_info.denom;
|
||||
return ((NSTimeInterval)timeInNanoseconds) / 1000000;
|
||||
}
|
||||
|
||||
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement)
|
||||
{
|
||||
Method originalMethod = class_getClassMethod(cls, original);
|
||||
IMP originalImplementation = method_getImplementation(originalMethod);
|
||||
const char *originalArgTypes = method_getTypeEncoding(originalMethod);
|
||||
|
||||
Method replacementMethod = class_getClassMethod(cls, replacement);
|
||||
IMP replacementImplementation = method_getImplementation(replacementMethod);
|
||||
const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
|
||||
|
||||
if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes))
|
||||
{
|
||||
class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
method_exchangeImplementations(originalMethod, replacementMethod);
|
||||
}
|
||||
}
|
||||
|
||||
void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
|
||||
{
|
||||
Method originalMethod = class_getInstanceMethod(cls, original);
|
||||
IMP originalImplementation = method_getImplementation(originalMethod);
|
||||
const char *originalArgTypes = method_getTypeEncoding(originalMethod);
|
||||
|
||||
Method replacementMethod = class_getInstanceMethod(cls, replacement);
|
||||
IMP replacementImplementation = method_getImplementation(replacementMethod);
|
||||
const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
|
||||
|
||||
if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes))
|
||||
{
|
||||
class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
method_exchangeImplementations(originalMethod, replacementMethod);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL RCTClassOverridesClassMethod(Class cls, SEL selector)
|
||||
{
|
||||
return RCTClassOverridesInstanceMethod(object_getClass(cls), selector);
|
||||
}
|
||||
|
||||
BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
|
||||
{
|
||||
unsigned int numberOfMethods;
|
||||
Method *methods = class_copyMethodList(cls, &numberOfMethods);
|
||||
for (unsigned int i = 0; i < numberOfMethods; i++)
|
||||
{
|
||||
if (method_getName(methods[i]) == selector)
|
||||
{
|
||||
free(methods);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
free(methods);
|
||||
return NO;
|
||||
}
|
||||
|
||||
void RCTEnumerateClasses(void (^block)(Class cls))
|
||||
{
|
||||
static Class *classes;
|
||||
static unsigned int classCount;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
classes = objc_copyClassList(&classCount);
|
||||
});
|
||||
|
||||
for (unsigned int i = 0; i < classCount; i++)
|
||||
{
|
||||
Class cls = classes[i];
|
||||
Class superclass = cls;
|
||||
while (superclass)
|
||||
{
|
||||
if (class_conformsToProtocol(superclass, @protocol(NSObject)))
|
||||
{
|
||||
block(cls);
|
||||
break;
|
||||
}
|
||||
superclass = class_getSuperclass(superclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData)
|
||||
{
|
||||
if (toStringify) {
|
||||
message = [NSString stringWithFormat:@"%@%@", message, toStringify];
|
||||
}
|
||||
NSMutableDictionary *error = [@{@"message": message} mutableCopy];
|
||||
if (extraData) {
|
||||
[error addEntriesFromDictionary:extraData];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData)
|
||||
{
|
||||
id error = RCTMakeError(message, toStringify, extraData);
|
||||
RCTLogError(@"\nError: %@", error);
|
||||
return error;
|
||||
}
|
||||
Reference in New Issue
Block a user