Updates Thu, 25 Jun

This commit is contained in:
Alex Kotliarskyi 2015-06-25 09:37:02 -07:00
commit cfe13f2c4e
39 changed files with 1814 additions and 1477 deletions

View File

@ -7,8 +7,6 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import <AdSupport/ASIdentifierManager.h>
#import "RCTBridgeModule.h" #import "RCTBridgeModule.h"
@interface RCTAdSupport : NSObject <RCTBridgeModule> @interface RCTAdSupport : NSObject <RCTBridgeModule>

View File

@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import <AdSupport/ASIdentifierManager.h>
#import "RCTAdSupport.h" #import "RCTAdSupport.h"
@implementation RCTAdSupport @implementation RCTAdSupport

View File

@ -217,6 +217,11 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
} }
NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:keypath]; NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:keypath];
#if !CGFLOAT_IS_DOUBLE
if ([fromValue isKindOfClass:[NSNumber class]]) {
fromValue = [NSNumber numberWithFloat:[(NSNumber *)fromValue doubleValue]];
}
#endif
CGFloat fromFields[count]; CGFloat fromFields[count];
[fromValue getValue:fromFields]; [fromValue getValue:fromFields];

View File

@ -50,6 +50,7 @@ var AndroidTextInputAttributes = {
multiline: true, multiline: true,
password: true, password: true,
placeholder: true, placeholder: true,
placeholderTextColor: true,
text: true, text: true,
testID: true, testID: true,
underlineColorAndroid: true, underlineColorAndroid: true,
@ -514,6 +515,7 @@ var TextInput = React.createClass({
onLayout={this.props.onLayout} onLayout={this.props.onLayout}
password={this.props.password || this.props.secureTextEntry} password={this.props.password || this.props.secureTextEntry}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
placeholderTextColor={this.props.placeholderTextColor}
text={this.state.bufferedValue} text={this.state.bufferedValue}
underlineColorAndroid={this.props.underlineColorAndroid} underlineColorAndroid={this.props.underlineColorAndroid}
children={children} children={children}

View File

@ -0,0 +1,55 @@
/**
* 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.
*
* Utility class to provide the component owner hierarchy to native code for
* debugging purposes.
*
* @providesModule RCTDebugComponentOwnership
* @flow
*/
'use strict';
var DebugComponentOwnershipModule = require('NativeModules').DebugComponentOwnershipModule;
var InspectorUtils = require('InspectorUtils');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
function componentToString(component) {
return component.getName ? component.getName() : 'Unknown';
}
function getRootTagForTag(tag: number): ?number {
var rootNodeID = ReactNativeTagHandles.tagToRootNodeID[tag];
if (!rootNodeID) {
return null;
}
var rootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootNodeID);
if (!rootID) {
return null;
}
return ReactNativeTagHandles.rootNodeIDToTag[rootID];
}
module.exports = {
/**
* Asynchronously returns the owner hierarchy as an array of strings. Request id is
* passed along to the native module so that the native module can identify the
* particular call instance.
*
* Example returned owner hierarchy: ['RootView', 'Dialog', 'TitleView', 'Text']
*/
getOwnerHierarchy: function(requestID: number, tag: number) {
var rootTag = getRootTagForTag(tag);
var instance = InspectorUtils.findInstanceByNativeTag(rootTag, tag);
var ownerHierarchy = instance ?
InspectorUtils.getOwnerHierarchy(instance).map(componentToString) :
null;
DebugComponentOwnershipModule.receiveOwnershipHierarchy(requestID, tag, ownerHierarchy);
},
};

View File

@ -23,6 +23,7 @@
/* globals GLOBAL: true, window: true */ /* globals GLOBAL: true, window: true */
// Just to make sure the JS gets packaged up. // Just to make sure the JS gets packaged up.
require('RCTDebugComponentOwnership');
require('RCTDeviceEventEmitter'); require('RCTDeviceEventEmitter');
require('PerformanceLogger'); require('PerformanceLogger');

View File

@ -285,7 +285,7 @@ RCT_EXPORT_MODULE()
}]; }];
id<RCTURLRequestHandler> handler = [handlers lastObject]; id<RCTURLRequestHandler> handler = [handlers lastObject];
if (!handler) { if (!handler) {
RCTLogError(@"No suitable request handler found for %@", request); RCTLogError(@"No suitable request handler found for %@", request.URL);
} }
return handler; return handler;
} }
@ -322,6 +322,9 @@ RCT_EXPORT_MODULE()
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]]; NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
if (request) { if (request) {
id<RCTURLRequestHandler> handler = [self handlerForRequest:request]; id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) {
return;
}
(void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) { (void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) {
if (data) { if (data) {
callback(nil, @{@"body": data, @"contentType": MIMEType}); callback(nil, @{@"body": data, @"contentType": MIMEType});

View File

@ -99,7 +99,14 @@ RCT_NOT_IMPLEMENTED(-init)
error = [[RCTRedBox sharedInstance] currentErrorMessage]; error = [[RCTRedBox sharedInstance] currentErrorMessage];
} }
[rootView removeFromSuperview]; [rootView removeFromSuperview];
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
}]];
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
vc.view = nil; vc.view = nil;
[[RCTRedBox sharedInstance] dismiss]; [[RCTRedBox sharedInstance] dismiss];
if (expectErrorBlock) { if (expectErrorBlock) {

View File

@ -0,0 +1,785 @@
/**
* 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 "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTContextExecutor.h"
#import "RCTFrameUpdate.h"
#import "RCTJavaScriptLoader.h"
#import "RCTLog.h"
#import "RCTModuleData.h"
#import "RCTModuleMethod.h"
#import "RCTPerformanceLogger.h"
#import "RCTPerfStats.h"
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTSourceCode.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
#define RCTAssertJSThread() \
RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTContextExecutor"] || \
[[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
@"This method must be called on JS thread")
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
};
RCT_EXTERN NSArray *RCTGetModuleClasses(void);
static id<RCTJavaScriptExecutor> RCTLatestExecutor = nil;
id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void);
id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void)
{
return RCTLatestExecutor;
}
@interface RCTBatchedBridge : RCTBridge
@property (nonatomic, weak) RCTBridge *parentBridge;
@end
@implementation RCTBatchedBridge
{
BOOL _loading;
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
NSMutableArray *_modules;
NSDictionary *_modulesByName;
CADisplayLink *_mainDisplayLink;
CADisplayLink *_jsDisplayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
RCTSparseArray *_scheduledCallbacks;
}
@synthesize valid = _valid;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
RCTAssertParam(bridge);
if ((self = [super initWithBundleURL:bridge.bundleURL
moduleProvider:bridge.moduleProvider
launchOptions:bridge.launchOptions])) {
_parentBridge = bridge;
/**
* Set Initial State
*/
_valid = YES;
_loading = YES;
_modules = [[NSMutableArray alloc] init];
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
if (RCT_DEV) {
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
/**
* Initialize and register bridge modules *before* adding the display link
* so we don't have threading issues
*/
[self registerModules];
/**
* Start the application script
*/
[self initJS];
}
return self;
}
RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
moduleProvider:(__unused RCTBridgeModuleProviderBlock)block
launchOptions:(__unused NSDictionary *)launchOptions)
- (void)setUp {}
- (void)bindKeys {}
- (void)reload
{
[_parentBridge reload];
}
- (Class)executorClass
{
return _parentBridge.executorClass ?: [RCTContextExecutor class];
}
- (void)setExecutorClass:(Class)executorClass
{
RCTAssertMainThread();
_parentBridge.executorClass = executorClass;
}
- (BOOL)isLoading
{
return _loading;
}
- (BOOL)isValid
{
return _valid;
}
- (void)registerModules
{
RCTAssertMainThread();
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in self.moduleProvider ? self.moduleProvider() : nil) {
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
}
// Instantiate modules
_modules = [[NSMutableArray alloc] init];
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
for (Class moduleClass in RCTGetModuleClasses()) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
// Check if module instance has already been registered for this name
id<RCTBridgeModule> module = modulesByName[moduleName];
if (module) {
// 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]);
}
if ([module class] != moduleClass) {
RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
"in the project, but name was already registered by class %@."
"That's fine if it's intentional - just letting you know.",
moduleClass, moduleName, [modulesByName[moduleName] class]);
}
} else {
// Module name hasn't been used before, so go ahead and instantiate
module = [[moduleClass alloc] init];
}
if (module) {
modulesByName[moduleName] = module;
}
}
// Store modules
_modulesByName = [modulesByName copy];
/**
* The executor is a bridge module, wait for it to be created and set it before
* any other module has access to the bridge
*/
_javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)];
RCTLatestExecutor = _javaScriptExecutor;
RCTSetExecutorID(_javaScriptExecutor);
[_javaScriptExecutor setUp];
// Set bridge
for (id<RCTBridgeModule> module in _modulesByName.allValues) {
if ([module respondsToSelector:@selector(setBridge:)]) {
module.bridge = self;
}
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
uid:@(_modules.count)
instance:module];
[_modules addObject:moduleData];
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:moduleData];
}
}
}
- (void)initJS
{
RCTAssertMainThread();
// Inject module data into JS context
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
for (RCTModuleData *moduleData in _modules) {
config[moduleData.name] = moduleData.config;
}
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": config,
}, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:
^(__unused id err) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
NSURL *bundleURL = _parentBridge.bundleURL;
if (_javaScriptExecutor == nil) {
/**
* HACK (tadeu): If it failed to connect to the debugger, set loading to NO
* so we can attempt to reload again.
*/
_loading = NO;
} else if (!bundleURL) {
// Allow testing without a script
dispatch_async(dispatch_get_main_queue(), ^{
_loading = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
});
} else {
RCTProfileBeginEvent();
RCTPerformanceLoggerStart(RCTPLScriptDownload);
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
RCTProfileEndEvent(@"JavaScript dowload", @"init,download", @[]);
_loading = NO;
if (!self.isValid) {
return;
}
[[RCTRedBox sharedInstance] dismiss];
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
if (error) {
NSArray *stack = [error userInfo][@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withDetails:[error localizedFailureReason]];
}
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo];
} else {
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) {
if (!loadError) {
/**
* Register the display link to start sending js calls after everything
* is setup
*/
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[loadError localizedDescription]
withDetails:[loadError localizedFailureReason]];
}
}];
}
}];
}
}
- (NSDictionary *)modules
{
RCTAssert(!self.isValid || _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;
}
#pragma mark - RCTInvalidating
- (void)invalidate
{
if (!self.isValid) {
return;
}
RCTAssertMainThread();
_valid = NO;
if (RCTLatestExecutor == _javaScriptExecutor) {
RCTLatestExecutor = nil;
}
void (^mainThreadInvalidate)(void) = ^{
RCTAssertMainThread();
[_mainDisplayLink invalidate];
_mainDisplayLink = nil;
// Invalidate modules
dispatch_group_t group = dispatch_group_create();
for (RCTModuleData *moduleData in _modules) {
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
[moduleData dispatchBlock:^{
[(id<RCTInvalidating>)moduleData.instance invalidate];
} dispatchGroup:group];
}
moduleData.queue = nil;
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
_modules = nil;
_modulesByName = nil;
_frameUpdateObservers = nil;
});
};
if (!_javaScriptExecutor) {
// No JS thread running
mainThreadInvalidate();
return;
}
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
/**
* JS Thread deallocations
*/
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
[_jsDisplayLink invalidate];
_jsDisplayLink = nil;
/**
* Main Thread deallocations
*/
dispatch_async(dispatch_get_main_queue(), mainThreadInvalidate);
}];
}
#pragma mark - RCTBridge methods
/**
* Public. Can be invoked from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."];
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[ids[0], ids[1], args ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
/**
* Private hack to support `setTimeout(fn, 0)`
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
RCTAssertJSThread();
dispatch_block_t block = ^{
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[@"JSTimersExecution", @"callTimers", @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
}
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginFlowEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTProfileEndFlowEvent();
RCTAssertJSThread();
if (scriptLoadError) {
onComplete(scriptLoadError);
return;
}
RCTProfileBeginEvent();
NSNumber *context = RCTGetExecutorID(_javaScriptExecutor);
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
method:@"flushedQueue"
arguments:@[]
context:context
callback:^(id json, NSError *error) {
RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{
@"json": RCTNullIfNil(json),
@"error": RCTNullIfNil(error),
});
[self _handleBuffer:json context:context];
onComplete(error);
}];
}];
}
#pragma mark - Payload Generation
/**
* TODO: Completely remove `context` - no longer needed
*/
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
[self _invokeAndProcessModule:module
method:method
arguments:args
context:RCTGetExecutorID(_javaScriptExecutor)];
}
/**
* Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
* on the JS thread, but only in non-batched mode.
*/
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
/**
* AnyThread
*/
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) {
return;
}
RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();)
id call = @{
@"js_args": @{
@"module": module,
@"method": method,
@"args": args,
},
@"context": context ?: @0,
RCT_IF_DEV(@"call_id": callID,)
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
strongSelf->_scheduledCallbacks[args[0]] = call;
} else {
[strongSelf->_scheduledCalls addObject:call];
}
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
}];
}
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
RCTAssertJSThread();
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, __unused NSError *error) {
if (!self.isValid) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self _handleBuffer:json context:context];
};
[_javaScriptExecutor executeJSCall:module
method:method
arguments:args
context:context
callback:processResponse];
}
#pragma mark - Payload Processing
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
{
RCTAssertJSThread();
if (buffer == nil || buffer == (id)kCFNull) {
return;
}
NSArray *requestsArray = [RCTConvert NSArray:buffer];
#if RCT_DEBUG
if (![buffer isKindOfClass:[NSArray class]]) {
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
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;
}
}
#endif
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
NSUInteger numRequests = [moduleIDs count];
if (RCT_DEBUG && (numRequests != methodIDs.count || numRequests != paramsArrays.count)) {
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
return;
}
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:_modules.count];
for (NSUInteger i = 0; i < numRequests; i++) {
RCTModuleData *moduleData = _modules[[moduleIDs[i] integerValue]];
NSMutableOrderedSet *set = [buckets objectForKey:moduleData];
if (!set) {
set = [[NSMutableOrderedSet alloc] init];
[buckets setObject:set forKey:moduleData];
}
[set addObject:@(i)];
}
for (RCTModuleData *moduleData in buckets) {
RCTProfileBeginFlowEvent();
[moduleData dispatchBlock:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
NSOrderedSet *calls = [buckets objectForKey:moduleData];
@autoreleasepool {
for (NSNumber *indexObj in calls) {
NSUInteger index = indexObj.unsignedIntegerValue;
[self _handleRequestNumber:index
moduleID:[moduleIDs[index] integerValue]
methodID:[methodIDs[index] integerValue]
params:paramsArrays[index]
context:context];
}
}
RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
}];
}
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
for (RCTModuleData *moduleData in _modules) {
if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) {
[moduleData dispatchBlock:^{
[moduleData.instance batchDidComplete];
}];
}
}
}
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID
methodID:(NSUInteger)methodID
params:(NSArray *)params
context:(NSNumber *)context
{
if (!self.isValid) {
return NO;
}
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
}
RCTProfileBeginEvent();
RCTModuleData *moduleData = _modules[moduleID];
if (RCT_DEBUG && !moduleData) {
RCTLogError(@"No module found for id '%zd'", moduleID);
return NO;
}
RCTModuleMethod *method = moduleData.methods[methodID];
if (RCT_DEBUG && !method) {
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, moduleData.name);
return NO;
}
@try {
[method invokeWithBridge:self module:moduleData.instance arguments:params context:context];
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, moduleData.name, params, exception);
if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
@throw exception;
}
}
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
@"module": method.moduleClassName,
@"method": method.JSMethodName,
@"selector": NSStringFromSelector(method.selector),
@"args": RCTJSONStringify(RCTNullIfNil(params), NULL),
});
return YES;
}
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertJSThread();
RCTProfileBeginEvent();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (RCTModuleData *moduleData in _frameUpdateObservers) {
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];)
RCTProfileBeginFlowEvent();
[moduleData dispatchBlock:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
[observer didUpdateFrame:frameUpdate];
RCTProfileEndEvent(name, @"objc_call,fps", nil);
}];
}
}
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
calls = [calls filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:
^BOOL(NSDictionary *call, __unused NSDictionary *bindings) {
return [call[@"context"] isEqualToNumber:currentExecutorID];
}]];
RCT_IF_DEV(
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
for (NSDictionary *call in calls) {
_RCTProfileEndFlowEvent(call[@"call_id"]);
}
)
if (calls.count > 0) {
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"processBatch"
arguments:@[[calls valueForKey:@"js_args"]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
dispatch_async(dispatch_get_main_queue(), ^{
[self.perfStats.jsGraph tick:displayLink.timestamp];
});
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
[self.perfStats.uiGraph tick:displayLink.timestamp];
}
- (void)startProfiling
{
RCTAssertMainThread();
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
RCTLogError(@"To run the profiler you must be running from the dev server");
return;
}
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileInit(self);
}];
}
- (void)stopProfiling
{
RCTAssertMainThread();
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
NSString *log = RCTProfileEnd(self);
NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
URLRequest.HTTPMethod = @"POST";
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSURLSessionTask *task =
[[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
completionHandler:
^(__unused NSData *data, __unused NSURLResponse *response, NSError *error) {
if (error) {
RCTLogError(@"%@", error.localizedDescription);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:@"Profile"
message:@"The profile has been generated, check the dev server log for instructions."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
});
}
}];
[task resume];
}];
}
@end

View File

@ -118,6 +118,11 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
*/ */
@property (nonatomic, readonly, getter=isLoading) BOOL loading; @property (nonatomic, readonly, getter=isLoading) BOOL loading;
/**
* The block passed in the constructor with pre-initialized modules
*/
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
/** /**
* Reload the bundle and reset executor & modules. Safe to call from any thread. * Reload the bundle and reset executor & modules. Safe to call from any thread.
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -78,7 +78,7 @@ extern dispatch_queue_t RCTJSThread;
* and the bridge will populate the methodQueue property for you automatically * and the bridge will populate the methodQueue property for you automatically
* when it initializes the module. * when it initializes the module.
*/ */
@property (nonatomic, weak, readonly) dispatch_queue_t methodQueue; @property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
/** /**
* Place this macro in your class implementation to automatically register * Place this macro in your class implementation to automatically register

View File

@ -635,17 +635,24 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
return nil; return nil;
} }
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) { if (RCT_DEBUG && ![json isKindOfClass:[NSString class]] && ![json isKindOfClass:[NSDictionary class]]) {
RCTLogConvertError(json, "an image"); RCTLogConvertError(json, "an image");
return nil; return nil;
} }
if ([json length] == 0) { UIImage *image;
return nil; NSString *path;
CGFloat scale = 0.0;
if ([json isKindOfClass:[NSString class]]) {
if ([json length] == 0) {
return nil;
}
path = json;
} else {
path = [self NSString:json[@"uri"]];
scale = [self CGFloat:json[@"scale"]];
} }
UIImage *image = nil;
NSString *path = json;
if ([path hasPrefix:@"data:"]) { if ([path hasPrefix:@"data:"]) {
NSURL *url = [NSURL URLWithString:path]; NSURL *url = [NSURL URLWithString:path];
NSData *imageData = [NSData dataWithContentsOfURL:url]; NSData *imageData = [NSData dataWithContentsOfURL:url];
@ -658,6 +665,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]]; image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]];
} }
} }
if (scale > 0) {
image = [UIImage imageWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
}
// NOTE: we don't warn about nil images because there are legitimate // 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 // case where we find out if a string is an image by using this method
return image; return image;

View File

@ -7,6 +7,10 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import <Foundation/Foundation.h>
@class CADisplayLink;
/** /**
* Interface containing the information about the last screen refresh. * Interface containing the information about the last screen refresh.
*/ */
@ -22,6 +26,8 @@
*/ */
@property (nonatomic, readonly) NSTimeInterval deltaTime; @property (nonatomic, readonly) NSTimeInterval deltaTime;
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink NS_DESIGNATED_INITIALIZER;
@end @end
/** /**

View 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 <QuartzCore/CADisplayLink.h>
#import "RCTFrameUpdate.h"
#import "RCTUtils.h"
@implementation RCTFrameUpdate
RCT_NOT_IMPLEMENTED(-init)
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
{
if ((self = [super init])) {
_timestamp = displayLink.timestamp;
_deltaTime = displayLink.duration;
}
return self;
}
@end

View 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 <Foundation/Foundation.h>
#import "RCTJavaScriptExecutor.h"
@interface RCTModuleData : NSObject
@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;
@property (nonatomic, strong, readonly) NSNumber *uid;
@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;
@property (nonatomic, strong, readonly) Class cls;
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, copy, readonly) NSArray *methods;
@property (nonatomic, copy, readonly) NSDictionary *config;
@property (nonatomic, strong) dispatch_queue_t queue;
- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
uid:(NSNumber *)uid
instance:(id<RCTBridgeModule>)instance NS_DESIGNATED_INITIALIZER;
- (void)dispatchBlock:(dispatch_block_t)block;
- (void)dispatchBlock:(dispatch_block_t)block dispatchGroup:(dispatch_group_t)group;
@end

146
React/Base/RCTModuleData.m Normal file
View File

@ -0,0 +1,146 @@
/**
* 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 "RCTModuleData.h"
#import "RCTBridge.h"
#import "RCTModuleMethod.h"
#import "RCTLog.h"
@implementation RCTModuleData
- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
uid:(NSNumber *)uid
instance:(id<RCTBridgeModule>)instance
{
if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor;
_uid = uid;
_instance = instance;
_cls = [instance class];
_name = RCTBridgeModuleNameForClass(_cls);
[self loadMethods];
[self generateConfig];
[self setQueue];
}
return self;
}
RCT_NOT_IMPLEMENTED(-init);
- (void)loadMethods
{
NSMutableArray *moduleMethods = [[NSMutableArray alloc] init];
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(_cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
NSArray *entries = ((NSArray *(*)(id, SEL))imp)(_cls, selector);
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
JSMethodName:entries[0]
moduleClass:_cls];
[moduleMethods addObject:moduleMethod];
}
}
free(methods);
_methods = [moduleMethods copy];
}
- (void)generateConfig
{
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
config[@"moduleID"] = _uid;
config[@"methods"] = [[NSMutableDictionary alloc] init];
if ([_instance respondsToSelector:@selector(constantsToExport)]) {
id consts = [_instance constantsToExport];
if (consts) {
config[@"constants"] = consts;
}
}
[_methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger idx, __unused BOOL *stop) {
config[@"methods"][method.JSMethodName] = @{
@"methodID": @(idx),
@"type": method.functionKind == RCTJavaScriptFunctionKindAsync ? @"remoteAsync" : @"remote",
};
}];
_config = [config copy];
}
- (void)setQueue
{
dispatch_queue_t queue = nil;
BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
if (implementsMethodQueue) {
queue = [_instance methodQueue];
}
if (!queue) {
// Need to cache queueNames because they aren't retained by dispatch_queue
static NSMutableDictionary *queueNames;
if (!queueNames) {
queueNames = [[NSMutableDictionary alloc] init];
}
NSString *queueName = queueNames[_name];
if (!queueName) {
queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", _name];
queueNames[_name] = queueName;
}
// Create new queue
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
// assign it to the module
if (implementsMethodQueue) {
@try {
[(id)_instance setValue:queue forKey:@"methodQueue"];
}
@catch (NSException *exception) {
RCTLogError(@"%@ is returning nil for it's methodQueue, which is not "
"permitted. You must either return a pre-initialized "
"queue, or @synthesize the methodQueue to let the bridge "
"create a queue for you.", _name);
}
}
}
_queue = queue;
}
- (void)dispatchBlock:(dispatch_block_t)block
{
[self dispatchBlock:block dispatchGroup:NULL];
}
- (void)dispatchBlock:(dispatch_block_t)block
dispatchGroup:(dispatch_group_t)group
{
if (_queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else if (_queue) {
if (group != NULL) {
dispatch_group_async(group, _queue, block);
} else {
dispatch_async(_queue, block);
}
}
}
@end

View 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 <Foundation/Foundation.h>
@class RCTBridge;
typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) {
RCTJavaScriptFunctionKindNormal,
RCTJavaScriptFunctionKindAsync,
};
@interface RCTModuleMethod : NSObject
@property (nonatomic, copy, readonly) NSString *moduleClassName;
@property (nonatomic, copy, readonly) NSString *JSMethodName;
@property (nonatomic, assign, readonly) SEL selector;
@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind;
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName
moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER;
- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
context:(NSNumber *)context;
@end

View File

@ -0,0 +1,288 @@
/**
* 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 "RCTModuleMethod.h"
#import <objc/message.h>
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@interface RCTBridge (RCTModuleMethod)
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
@end
@implementation RCTModuleMethod
{
Class _moduleClass;
SEL _selector;
NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks;
}
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName
moduleClass:(Class)moduleClass
{
if ((self = [super init])) {
static NSRegularExpression *typeRegex;
static NSRegularExpression *selectorRegex;
if (!typeRegex) {
NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))";
NSString *constPattern = @"(?:const)";
NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern];
NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern];
typeRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
selectorRegex = [[NSRegularExpression alloc] initWithPattern:@"(?<=:).*?(?=[a-zA-Z_]+:|$)" options:0 error:NULL];
}
NSMutableArray *argumentNames = [NSMutableArray array];
[typeRegex enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
[argumentNames addObject:argumentName];
}];
// Remove the parameters' type and name
objCMethodName = [selectorRegex stringByReplacingMatchesInString:objCMethodName
options:0
range:NSMakeRange(0, objCMethodName.length)
withTemplate:@""];
// Remove any spaces since `selector : (Type)name` is a valid syntax
objCMethodName = [objCMethodName stringByReplacingOccurrencesOfString:@" " withString:@""];
_moduleClass = moduleClass;
_moduleClassName = NSStringFromClass(_moduleClass);
_selector = NSSelectorFromString(objCMethodName);
_JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
NSString *methodName = NSStringFromSelector(_selector);
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.length) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName;
});
// Get method signature
_methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(__unused RCTBridge *bridge, __unused NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
}]; \
void (^addBlockArgument)(void) = ^{
RCT_ARG_BLOCK(
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing id value = (json ? ^(NSArray *args) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, args]];
} : ^(__unused NSArray *unused) {});
)
};
void (^defaultCase)(const char *) = ^(const char *argumentType) {
static const char *blockType = @encode(typeof(^{}));
if (!strcmp(argumentType, blockType)) {
addBlockArgument();
} else {
RCT_ARG_BLOCK( id value = json; )
}
};
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
NSString *argumentName = argumentNames[i - 2];
SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]);
if ([RCTConvert respondsToSelector:selector]) {
switch (argumentType[0]) {
#define RCT_CONVERT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
RCT_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
case '{': {
[argumentBlocks addObject:^(__unused RCTBridge *bridge, __unused NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
void *returnValue = malloc(methodSignature.methodReturnLength);
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[_invocation setTarget:[RCTConvert class]];
[_invocation setSelector:selector];
[_invocation setArgument:&json atIndex:2];
[_invocation invoke];
[_invocation getReturnValue:returnValue];
[invocation setArgument:returnValue atIndex:index];
free(returnValue);
}];
break;
}
default:
defaultCase(argumentType);
}
} else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
addBlockArgument();
} else if ([argumentName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
_moduleClassName, objCMethodName);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing RCTPromiseResolveBlock value = (^(id result) {
NSArray *arguments = result ? @[result] : @[];
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, arguments]];
});
)
_functionKind = RCTJavaScriptFunctionKindAsync;
} else if ([argumentName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(i == numberOfArguments - 1,
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
_moduleClassName, objCMethodName);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) {
NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, @[errorJSON]]];
});
)
_functionKind = RCTJavaScriptFunctionKindAsync;
} else {
// Unknown argument type
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
" to support this type.", argumentName, [self methodName]);
}
}
_argumentBlocks = [argumentBlocks copy];
}
return self;
}
- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
context:(NSNumber *)context
{
if (RCT_DEBUG) {
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", [self methodName], [module class]);
// Safety check
if (arguments.count != _argumentBlocks.count) {
NSInteger actualCount = arguments.count;
NSInteger expectedCount = _argumentBlocks.count;
// Subtract the implicit Promise resolver and rejecter functions for implementations of async functions
if (_functionKind == RCTJavaScriptFunctionKindAsync) {
actualCount -= 2;
expectedCount -= 2;
}
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
actualCount, expectedCount);
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];
[invocation retainArguments];
// Set arguments
NSUInteger index = 0;
for (id json in arguments) {
id arg = RCTNilIfNull(json);
void (^block)(RCTBridge *, NSNumber *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
block(bridge, context, invocation, index + 2, arg);
index++;
}
// Invoke method
[invocation invokeWithTarget:module];
}
- (NSString *)methodName
{
return [NSString stringWithFormat:@"-[%@ %@]", _moduleClass,
NSStringFromSelector(_selector)];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@();>",
[self class], self, [self methodName], _JSMethodName];
}
@end

View File

@ -18,6 +18,7 @@
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTDefines.h" #import "RCTDefines.h"
#import "RCTModuleData.h"
#import "RCTUtils.h" #import "RCTUtils.h"
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling"; NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
@ -98,12 +99,6 @@ NSDictionary *RCTProfileGetMemoryUsage(void)
#pragma mark - Module hooks #pragma mark - Module hooks
@interface RCTBridge (Private)
- (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module;
@end
static const char *RCTProfileProxyClassName(Class); static const char *RCTProfileProxyClassName(Class);
static const char *RCTProfileProxyClassName(Class class) static const char *RCTProfileProxyClassName(Class class)
{ {
@ -152,9 +147,9 @@ static IMP RCTProfileMsgForward(NSObject *self, SEL selector)
static void RCTProfileHookModules(RCTBridge *); static void RCTProfileHookModules(RCTBridge *);
static void RCTProfileHookModules(RCTBridge *bridge) static void RCTProfileHookModules(RCTBridge *bridge)
{ {
for (id<RCTBridgeModule> module in bridge.modules.allValues) { for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
[bridge dispatchBlock:^{ [moduleData dispatchBlock:^{
Class moduleClass = object_getClass(module); Class moduleClass = moduleData.cls;
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
unsigned int methodCount; unsigned int methodCount;
@ -167,7 +162,7 @@ static void RCTProfileHookModules(RCTBridge *bridge)
} }
IMP originalIMP = method_getImplementation(method); IMP originalIMP = method_getImplementation(method);
const char *returnType = method_getTypeEncoding(method); const char *returnType = method_getTypeEncoding(method);
class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType); class_addMethod(proxyClass, selector, RCTProfileMsgForward(moduleData.instance, selector), returnType);
class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType); class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType);
} }
free(methods); free(methods);
@ -185,24 +180,24 @@ static void RCTProfileHookModules(RCTBridge *bridge)
} }
objc_registerClassPair(proxyClass); objc_registerClassPair(proxyClass);
object_setClass(module, proxyClass); object_setClass(moduleData.instance, proxyClass);
} forModule:module]; }];
} }
} }
void RCTProfileUnhookModules(RCTBridge *); void RCTProfileUnhookModules(RCTBridge *);
void RCTProfileUnhookModules(RCTBridge *bridge) void RCTProfileUnhookModules(RCTBridge *bridge)
{ {
for (id<RCTBridgeModule> module in bridge.modules.allValues) { for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
[bridge dispatchBlock:^{ [moduleData dispatchBlock:^{
RCTProfileLock( RCTProfileLock(
Class proxyClass = object_getClass(module); Class proxyClass = object_getClass(moduleData.instance);
if (module.class != proxyClass) { if (moduleData.cls != proxyClass) {
object_setClass(module, module.class); object_setClass(moduleData.instance, moduleData.cls);
objc_disposeClassPair(proxyClass); objc_disposeClassPair(proxyClass);
} }
); );
} forModule:module]; }];
}; };
} }

View File

@ -61,3 +61,5 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
// Convert nil values to NSNull, and vice-versa // Convert nil values to NSNull, and vice-versa
RCT_EXTERN id RCTNullIfNil(id value); RCT_EXTERN id RCTNullIfNil(id value);
RCT_EXTERN id RCTNilIfNull(id value); RCT_EXTERN id RCTNilIfNull(id value);
RCT_EXTERN NSDictionary *RCTJSErrorFromNSError(NSError *error);

View File

@ -285,3 +285,24 @@ id RCTNilIfNull(id value)
{ {
return value == (id)kCFNull ? nil : value; return value == (id)kCFNull ? nil : value;
} }
// TODO: Can we just replace RCTMakeError with this function instead?
NSDictionary *RCTJSErrorFromNSError(NSError *error)
{
NSString *errorMessage;
NSArray *stackTrace = [NSThread callStackSymbols];
NSMutableDictionary *errorInfo =
[NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"];
if (error) {
errorMessage = error.localizedDescription ?: @"Unknown error from a native module";
errorInfo[@"domain"] = error.domain ?: RCTErrorDomain;
errorInfo[@"code"] = @(error.code);
} else {
errorMessage = @"Unknown error from a native module";
errorInfo[@"domain"] = RCTErrorDomain;
errorInfo[@"code"] = @-1;
}
return RCTMakeError(errorMessage, nil, errorInfo);
}

View File

@ -810,7 +810,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
viewName:(NSString *)viewName viewName:(NSString *)viewName
rootTag:(NSNumber *)rootTag rootTag:(__unused NSNumber *)rootTag
props:(NSDictionary *)props) props:(NSDictionary *)props)
{ {
RCTViewManager *manager = _viewManagers[viewName]; RCTViewManager *manager = _viewManagers[viewName];
@ -1222,7 +1222,7 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
* this in order to determine if scrolling is appropriate. * this in order to determine if scrolling is appropriate.
*/ */
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
blockNativeResponder:(BOOL)blockNativeResponder) blockNativeResponder:(__unused BOOL)blockNativeResponder)
{ {
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
_jsResponder = viewRegistry[reactTag]; _jsResponder = viewRegistry[reactTag];

View File

@ -52,6 +52,10 @@
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; }; 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; }; 146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; };
14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; };
14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; };
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; };
14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */; };
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; }; 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; }; 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; }; 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; };
@ -187,6 +191,12 @@
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; }; 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; };
146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = "<group>"; }; 146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = "<group>"; };
146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = "<group>"; }; 146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = "<group>"; };
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = "<group>"; };
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = "<group>"; };
14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleData.h; sourceTree = "<group>"; };
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleData.m; sourceTree = "<group>"; };
14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFrameUpdate.m; sourceTree = "<group>"; };
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBatchedBridge.m; sourceTree = "<group>"; };
14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = "<group>"; }; 14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = "<group>"; };
14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = "<group>"; }; 14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = "<group>"; };
14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; }; 14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; };
@ -444,6 +454,12 @@
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */, 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */,
142014171B32094000CC17BA /* RCTPerformanceLogger.m */, 142014171B32094000CC17BA /* RCTPerformanceLogger.m */,
142014181B32094000CC17BA /* RCTPerformanceLogger.h */, 142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */,
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */,
14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */,
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */,
); );
path = Base; path = Base;
sourceTree = "<group>"; sourceTree = "<group>";
@ -526,12 +542,14 @@
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */, 13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */, 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */, 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */, 131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */,
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */, 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */, 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */, 83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */,
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */, 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */, 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */,
@ -554,8 +572,10 @@
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */, 1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */, 13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */,
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */, 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */,
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */,
142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */,
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,

View File

@ -11,7 +11,7 @@
@interface RCTTabBarItem : UIView @interface RCTTabBarItem : UIView
@property (nonatomic, copy) NSString *icon; @property (nonatomic, copy) id icon;
@property (nonatomic, assign, getter=isSelected) BOOL selected; @property (nonatomic, assign, getter=isSelected) BOOL selected;
@property (nonatomic, readonly) UITabBarItem *barItem; @property (nonatomic, readonly) UITabBarItem *barItem;

View File

@ -25,7 +25,7 @@
return _barItem; return _barItem;
} }
- (void)setIcon:(NSString *)icon - (void)setIcon:(id)icon
{ {
static NSDictionary *systemIcons; static NSDictionary *systemIcons;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
@ -54,7 +54,7 @@
UIImage *image = [RCTConvert UIImage:_icon]; UIImage *image = [RCTConvert UIImage:_icon];
UITabBarItem *oldItem = _barItem; UITabBarItem *oldItem = _barItem;
if (image) { if (image) {
// Recreate barItem if previous item was a system icon // Recreate barItem if previous item was a system icon
if (wasSystemIcon) { if (wasSystemIcon) {
_barItem = nil; _barItem = nil;

View File

@ -22,7 +22,7 @@ RCT_EXPORT_MODULE()
} }
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL); RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
RCT_EXPORT_VIEW_PROPERTY(icon, NSString); RCT_EXPORT_VIEW_PROPERTY(icon, id);
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage); RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage);
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString); RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString);
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem) RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)

View File

@ -39,6 +39,7 @@ RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL)
RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
{ {
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)]; view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];

View File

@ -47,6 +47,7 @@
"dependencies": { "dependencies": {
"absolute-path": "0.0.0", "absolute-path": "0.0.0",
"babel": "5.4.3", "babel": "5.4.3",
"babel-core": "^5.6.4",
"bluebird": "^2.9.21", "bluebird": "^2.9.21",
"chalk": "^1.0.0", "chalk": "^1.0.0",
"connect": "2.8.3", "connect": "2.8.3",

View File

@ -13,6 +13,8 @@ jest
.dontMock('crypto') .dontMock('crypto')
.dontMock('absolute-path') .dontMock('absolute-path')
.dontMock('../docblock') .dontMock('../docblock')
.dontMock('../../crawlers')
.dontMock('../../crawlers/node')
.dontMock('../../replacePatterns') .dontMock('../../replacePatterns')
.dontMock('../../../lib/getAssetDataFromName') .dontMock('../../../lib/getAssetDataFromName')
.dontMock('../../fastfs') .dontMock('../../fastfs')
@ -22,6 +24,8 @@ jest
.dontMock('../../Package') .dontMock('../../Package')
.dontMock('../../ModuleCache'); .dontMock('../../ModuleCache');
const Promise = require('promise');
jest.mock('fs'); jest.mock('fs');
describe('DependencyGraph', function() { describe('DependencyGraph', function() {
@ -36,7 +40,8 @@ describe('DependencyGraph', function() {
fileWatcher = { fileWatcher = {
on: function() { on: function() {
return this; return this;
} },
isWatchman: () => Promise.resolve(false)
}; };
}); });

View File

@ -8,17 +8,19 @@
*/ */
'use strict'; 'use strict';
const path = require('path'); const Activity = require('../../Activity');
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const Fastfs = require('../fastfs'); const Fastfs = require('../fastfs');
const ModuleCache = require('../ModuleCache'); const ModuleCache = require('../ModuleCache');
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const declareOpts = require('../../lib/declareOpts');
const isAbsolutePath = require('absolute-path');
const debug = require('debug')('DependencyGraph');
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
const util = require('util');
const Promise = require('promise'); const Promise = require('promise');
const _ = require('underscore'); const _ = require('underscore');
const crawl = require('../crawlers');
const debug = require('debug')('DependencyGraph');
const declareOpts = require('../../lib/declareOpts');
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const util = require('util');
const validateOpts = declareOpts({ const validateOpts = declareOpts({
roots: { roots: {
@ -68,13 +70,18 @@ class DependencyGraph {
return this._loading; return this._loading;
} }
const modulePattern = new RegExp( const crawlActivity = Activity.startEvent('fs crawl');
'\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
); this._crawling = crawl(allRoots, {
ignore: this._opts.ignoreFilePath,
exts: ['js', 'json'].concat(this._opts.assetExts),
fileWatcher: this._opts.fileWatcher,
});
this._crawling.then((files) => Activity.endEvent(crawlActivity));
this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, {
pattern: modulePattern,
ignore: this._opts.ignoreFilePath, ignore: this._opts.ignoreFilePath,
crawling: this._crawling,
}); });
this._fastfs.on('change', this._processFileChange.bind(this)); this._fastfs.on('change', this._processFileChange.bind(this));
@ -454,14 +461,10 @@ class DependencyGraph {
this._assetMap_DEPRECATED = Object.create(null); this._assetMap_DEPRECATED = Object.create(null);
const pattern = new RegExp(
'\.(' + this._opts.assetExts.join('|') + ')$'
);
const fastfs = new Fastfs( const fastfs = new Fastfs(
this._opts.assetRoots_DEPRECATED, this._opts.assetRoots_DEPRECATED,
this._opts.fileWatcher, this._opts.fileWatcher,
{ pattern, ignore: this._opts.ignoreFilePath } { ignore: this._opts.ignoreFilePath, crawling: this._crawling }
); );
fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this)); fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this));

View File

@ -0,0 +1,36 @@
'use strict';
const nodeCrawl = require('./node');
//const watchmanCrawl = require('./watchman');
function crawl(roots, options) {
return nodeCrawl(roots, options);
// Although, in theory, watchman should be much faster;
// there is currently a bottleneck somewhere in the
// encoding/decoding that is causing it to be slower
// than node crawling. However, this should be fixed soon.
// https://github.com/facebook/watchman/issues/113
/*
const {fileWatcher} = options;
return fileWatcher.isWatchman().then(isWatchman => {
console.log(isWatchman);
if (!isWatchman) {
return false;
}
// Make sure we're dealing with a version of watchman
// that's using `watch-project`
// TODO(amasad): properly expose (and document) used sane internals.
return fileWatcher.getWatchers().then(([watcher]) => !!watcher.watchProjectInfo.root);
}).then(isWatchman => {
if (isWatchman) {
return watchmanCrawl(roots, options);
}
return nodeCrawl(roots, options);
});*/
}
module.exports = crawl;

View File

@ -0,0 +1,61 @@
'use strict';
const Promise = require('promise');
const debug = require('debug')('DependencyGraph');
const fs = require('fs');
const path = require('path');
const readDir = Promise.denodeify(fs.readdir);
const stat = Promise.denodeify(fs.stat);
function nodeRecReadDir(roots, {ignore, exts}) {
const queue = roots.slice();
const retFiles = [];
const extPattern = new RegExp(
'\.(' + exts.join('|') + ')$'
);
function search() {
const currDir = queue.shift();
if (!currDir) {
return Promise.resolve();
}
return readDir(currDir)
.then(files => files.map(f => path.join(currDir, f)))
.then(files => Promise.all(
files.map(f => stat(f).catch(handleBrokenLink))
).then(stats => [
// Remove broken links.
files.filter((file, i) => !!stats[i]),
stats.filter(Boolean),
]))
.then(([files, stats]) => {
files.forEach((filePath, i) => {
if (ignore(filePath)) {
return;
}
if (stats[i].isDirectory()) {
queue.push(filePath);
return;
}
if (filePath.match(extPattern)) {
retFiles.push(filePath);
}
});
return search();
});
}
return search().then(() => retFiles);
}
function handleBrokenLink(e) {
debug('WARNING: error stating, possibly broken symlink', e.message);
return Promise.resolve();
}
module.exports = nodeRecReadDir;

View File

@ -0,0 +1,70 @@
'use strict';
const Promise = require('promise');
const path = require('path');
function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
const files = [];
return Promise.all(
roots.map(
root => fileWatcher.getWatcherForRoot(root)
)
).then(
watchers => {
// All watchman roots for all watches we have.
const watchmanRoots = watchers.map(
watcher => watcher.watchProjectInfo.root
);
// Actual unique watchers (because we use watch-project we may end up with
// duplicate "real" watches, and that's by design).
// TODO(amasad): push this functionality into the `FileWatcher`.
const uniqueWatchers = watchers.filter(
(watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i
);
return Promise.all(
uniqueWatchers.map(watcher => {
const watchedRoot = watcher.watchProjectInfo.root;
// Build up an expression to filter the output by the relevant roots.
const dirExpr = ['anyof'];
for (let i = 0; i < roots.length; i++) {
const root = roots[i];
if (isDescendant(watchedRoot, root)) {
dirExpr.push(['dirname', path.relative(watchedRoot, root)]);
}
}
const cmd = Promise.promisify(watcher.client.command.bind(watcher.client));
return cmd(['query', watchedRoot, {
'suffix': exts,
'expression': ['allof', ['type', 'f'], 'exists', dirExpr],
'fields': ['name'],
}]).then(resp => {
if ('warning' in resp) {
console.warn('watchman warning: ', resp.warning);
}
resp.files.forEach(filePath => {
filePath = path.join(
watchedRoot,
filePath
);
if (!ignore(filePath)) {
files.push(filePath);
}
return false;
});
});
})
);
}).then(() => files);
}
function isDescendant(root, child) {
return path.relative(root, child).indexOf('..') !== 0;
}
module.exports = watchmanRecReadDir;

View File

@ -4,28 +4,46 @@ const Promise = require('promise');
const {EventEmitter} = require('events'); const {EventEmitter} = require('events');
const _ = require('underscore'); const _ = require('underscore');
const debug = require('debug')('DependencyGraph');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const readDir = Promise.denodeify(fs.readdir);
const readFile = Promise.denodeify(fs.readFile); const readFile = Promise.denodeify(fs.readFile);
const stat = Promise.denodeify(fs.stat); const stat = Promise.denodeify(fs.stat);
const hasOwn = Object.prototype.hasOwnProperty; const hasOwn = Object.prototype.hasOwnProperty;
class Fastfs extends EventEmitter { class Fastfs extends EventEmitter {
constructor(roots, fileWatcher, {ignore, pattern}) { constructor(roots, fileWatcher, {ignore, crawling}) {
super(); super();
this._fileWatcher = fileWatcher; this._fileWatcher = fileWatcher;
this._ignore = ignore; this._ignore = ignore;
this._pattern = pattern;
this._roots = roots.map(root => new File(root, { isDir: true })); this._roots = roots.map(root => new File(root, { isDir: true }));
this._fastPaths = Object.create(null); this._fastPaths = Object.create(null);
this._crawling = crawling;
} }
build() { build() {
const queue = this._roots.slice(); const rootsPattern = new RegExp(
return this._search(queue).then(() => { '^(' + this._roots.map(root => escapeRegExp(root.path)).join('|') + ')'
);
return this._crawling.then(files => {
files.forEach(filePath => {
if (filePath.match(rootsPattern)) {
const newFile = new File(filePath, { isDir: false });
const parent = this._fastPaths[path.dirname(filePath)];
if (parent) {
parent.addChild(newFile);
} else {
this._add(newFile);
for (let file = newFile; file; file = file.parent) {
if (!this._fastPaths[file.path]) {
this._fastPaths[file.path] = file;
}
}
}
}
});
this._fileWatcher.on('all', this._processFileChange.bind(this)); this._fileWatcher.on('all', this._processFileChange.bind(this));
}); });
} }
@ -134,32 +152,6 @@ class Fastfs extends EventEmitter {
this._getAndAssertRoot(file.path).addChild(file); this._getAndAssertRoot(file.path).addChild(file);
} }
_search(queue) {
const dir = queue.shift();
if (!dir) {
return Promise.resolve();
}
return readAndStatDir(dir.path).then(([filePaths, stats]) => {
filePaths.forEach((filePath, i) => {
if (this._ignore(filePath)) {
return;
}
if (stats[i].isDirectory()) {
queue.push(
new File(filePath, { isDir: true, fstat: stats[i] })
);
return;
}
if (filePath.match(this._pattern)) {
this._add(new File(filePath, { fstat: stats[i] }));
}
});
return this._search(queue);
});
}
_processFileChange(type, filePath, root, fstat) { _processFileChange(type, filePath, root, fstat) {
const absPath = path.join(root, filePath); const absPath = path.join(root, filePath);
@ -182,10 +174,7 @@ class Fastfs extends EventEmitter {
delete this._fastPaths[path.normalize(absPath)]; delete this._fastPaths[path.normalize(absPath)];
if (type !== 'delete') { if (type !== 'delete') {
this._add(new File(absPath, { this._add(new File(absPath, { isDir: false }));
isDir: false,
fstat
}));
} }
this.emit('change', type, filePath, root, fstat); this.emit('change', type, filePath, root, fstat);
@ -193,16 +182,12 @@ class Fastfs extends EventEmitter {
} }
class File { class File {
constructor(filePath, {isDir, fstat}) { constructor(filePath, { isDir }) {
this.path = filePath; this.path = filePath;
this.isDir = Boolean(isDir); this.isDir = Boolean(isDir);
if (this.isDir) { if (this.isDir) {
this.children = Object.create(null); this.children = Object.create(null);
} }
if (fstat) {
this._stat = Promise.resolve(fstat);
}
} }
read() { read() {
@ -290,21 +275,8 @@ function isDescendant(root, child) {
return path.relative(root, child).indexOf('..') !== 0; return path.relative(root, child).indexOf('..') !== 0;
} }
function readAndStatDir(dir) { function escapeRegExp(str) {
return readDir(dir) return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
.then(files => Promise.all(files.map(f => path.join(dir, f))))
.then(files => Promise.all(
files.map(f => stat(f).catch(handleBrokenLink))
).then(stats => [
// Remove broken links.
files.filter((file, i ) => !!stats[i]),
stats.filter(Boolean),
]));
}
function handleBrokenLink(e) {
debug('WARNING: error stating, possibly broken symlink', e.message);
return Promise.resolve();
} }
module.exports = Fastfs; module.exports = Fastfs;

View File

@ -1,2 +1,5 @@
/* eslint global-strict:0 */ /* eslint global-strict:0 */
__DEV__ = false; __DEV__ = false;
/* global __BUNDLE_START_TIME__:true */
__BUNDLE_START_TIME__ = Date.now();

View File

@ -1,2 +1,5 @@
/* eslint global-strict:0 */ /* eslint global-strict:0 */
__DEV__ = true; __DEV__ = true;
/* global __BUNDLE_START_TIME__:true */
__BUNDLE_START_TIME__ = Date.now();

View File

@ -33,7 +33,7 @@ describe('FileWatcher', function() {
pit('it should get the watcher instance when ready', function() { pit('it should get the watcher instance when ready', function() {
var fileWatcher = new FileWatcher(['rootDir']); var fileWatcher = new FileWatcher(['rootDir']);
return fileWatcher._loading.then(function(watchers) { return fileWatcher.getWatchers().then(function(watchers) {
watchers.forEach(function(watcher) { watchers.forEach(function(watcher) {
expect(watcher instanceof Watcher).toBe(true); expect(watcher instanceof Watcher).toBe(true);
}); });
@ -48,7 +48,7 @@ describe('FileWatcher', function() {
var fileWatcher = new FileWatcher(['rootDir']); var fileWatcher = new FileWatcher(['rootDir']);
var handler = jest.genMockFn(); var handler = jest.genMockFn();
fileWatcher.on('all', handler); fileWatcher.on('all', handler);
return fileWatcher._loading.then(function(){ return fileWatcher.getWatchers().then(function(){
cb(1, 2, 3, 4); cb(1, 2, 3, 4);
jest.runAllTimers(); jest.runAllTimers();
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);

View File

@ -8,13 +8,14 @@
*/ */
'use strict'; 'use strict';
var EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
var sane = require('sane'); const sane = require('sane');
var Promise = require('promise'); const Promise = require('promise');
var util = require('util'); const exec = require('child_process').exec;
var exec = require('child_process').exec;
var detectingWatcherClass = new Promise(function(resolve) { const MAX_WAIT_TIME = 25000;
const detectingWatcherClass = new Promise(function(resolve) {
exec('which watchman', function(err, out) { exec('which watchman', function(err, out) {
if (err || out.length === 0) { if (err || out.length === 0) {
resolve(sane.NodeWatcher); resolve(sane.NodeWatcher);
@ -24,53 +25,76 @@ var detectingWatcherClass = new Promise(function(resolve) {
}); });
}); });
module.exports = FileWatcher; let inited = false;
var MAX_WAIT_TIME = 25000; class FileWatcher extends EventEmitter {
// Singleton constructor(rootConfigs) {
var fileWatcher = null; if (inited) {
throw new Error('FileWatcher can only be instantiated once');
}
inited = true;
function FileWatcher(rootConfigs) { super();
if (fileWatcher) { this._watcherByRoot = Object.create(null);
// This allows us to optimize watching in the future by merging roots etc.
throw new Error('FileWatcher can only be instantiated once'); this._loading = Promise.all(
rootConfigs.map(createWatcher)
).then(watchers => {
watchers.forEach((watcher, i) => {
this._watcherByRoot[rootConfigs[i].dir] = watcher;
watcher.on(
'all',
// args = (type, filePath, root, stat)
(...args) => this.emit('all', ...args)
);
});
return watchers;
});
this._loading.done();
} }
fileWatcher = this; getWatchers() {
return this._loading;
}
this._loading = Promise.all( getWatcherForRoot(root) {
rootConfigs.map(createWatcher) return this._loading.then(() => this._watcherByRoot[root]);
).then(function(watchers) { }
watchers.forEach(function(watcher) {
watcher.on('all', function(type, filepath, root, stat) { isWatchman() {
fileWatcher.emit('all', type, filepath, root, stat); return detectingWatcherClass.then(
}); Watcher => Watcher === sane.WatchmanWatcher
}); );
return watchers; }
});
this._loading.done(); end() {
return this._loading.then(
(watchers) => watchers.map(
watcher => Promise.denodeify(watcher.close).call(watcher)
)
);
}
static createDummyWatcher() {
const ev = new EventEmitter();
ev.end = function() {
return Promise.resolve();
};
return ev;
}
} }
util.inherits(FileWatcher, EventEmitter);
FileWatcher.prototype.end = function() {
return this._loading.then(function(watchers) {
watchers.forEach(function(watcher) {
return Promise.denodeify(watcher.close).call(watcher);
});
});
};
function createWatcher(rootConfig) { function createWatcher(rootConfig) {
return detectingWatcherClass.then(function(Watcher) { return detectingWatcherClass.then(function(Watcher) {
var watcher = new Watcher(rootConfig.dir, { const watcher = new Watcher(rootConfig.dir, {
glob: rootConfig.globs, glob: rootConfig.globs,
dot: false, dot: false,
}); });
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var rejectTimeout = setTimeout(function() { const rejectTimeout = setTimeout(function() {
reject(new Error([ reject(new Error([
'Watcher took too long to load', 'Watcher took too long to load',
'Try running `watchman version` from your terminal', 'Try running `watchman version` from your terminal',
@ -86,10 +110,4 @@ function createWatcher(rootConfig) {
}); });
} }
FileWatcher.createDummyWatcher = function() { module.exports = FileWatcher;
var ev = new EventEmitter();
ev.end = function() {
return Promise.resolve();
};
return ev;
};