Updates from Fri 24 Apr

This commit is contained in:
Spencer Ahrens 2015-04-24 11:46:18 -07:00
commit 21b4b5b352
149 changed files with 3608 additions and 1612 deletions

View File

@ -22,24 +22,33 @@
{
NSURL *jsCodeLocation;
// Loading JavaScript code - uncomment the one you want.
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
// OPTION 1
// Load from development server. Start the server from the repository root:
//
// $ npm start
//
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
// iOS device are on the same Wi-Fi network.
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle"];
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle, run
//
// $ curl http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle -o main.jsbundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
/**
* OPTION 2
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
* to your Xcode project folder in the terminal, and run
*
* $ curl 'http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle' -o main.jsbundle
*
* then add the `main.jsbundle` file to your project and uncomment this line:
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"Game2048"

View File

@ -23,24 +23,33 @@
{
NSURL *jsCodeLocation;
// Loading JavaScript code - uncomment the one you want.
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
// OPTION 1
// Load from development server. Start the server from the repository root:
//
// $ npm start
//
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
// iOS device are on the same Wi-Fi network.
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle"];
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle, run
//
// $ curl http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle -o main.jsbundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
/**
* OPTION 2
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
* to your Xcode project folder in the terminal, and run
*
* $ curl 'http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle' -o main.jsbundle
*
* then add the `main.jsbundle` file to your project and uncomment this line:
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"MoviesApp"

View File

@ -17,25 +17,33 @@
{
NSURL *jsCodeLocation;
// Loading JavaScript code - uncomment the one you want.
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
// OPTION 1
// Load from development server. Start the server from the repository root:
//
// $ npm start
//
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
// iOS device are on the same Wi-Fi network.
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle"];
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle,
// from the root of your project directory, run
//
// $ react-native bundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
/**
* OPTION 2
* Load from pre-bundled file on disk. To re-generate the static bundle
* from the root of your project directory, run
*
* $ react-native bundle --minify
*
* see http://facebook.github.io/react-native/docs/runningondevice.html
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"SampleApp"

View File

@ -1,5 +1,8 @@
// Offline JS
// To re-generate the offline bundle, run this from root of your project
// $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
// To re-generate the offline bundle, run this from the root of your project:
//
// $ react-native bundle --minify
//
// See http://facebook.github.io/react-native/docs/runningondevice.html for more details.
throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions');

View File

@ -22,24 +22,33 @@
{
NSURL *jsCodeLocation;
// Loading JavaScript code - uncomment the one you want.
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
// OPTION 1
// Load from development server. Start the server from the repository root:
//
// $ npm start
//
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
// iOS device are on the same Wi-Fi network.
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle"];
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle, run
//
// $ curl http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle -o main.jsbundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
/**
* OPTION 2
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
* to your Xcode project folder in the terminal, and run
*
* $ curl 'http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle' -o main.jsbundle
*
* then add the `main.jsbundle` file to your project and uncomment this line:
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"TicTacToeApp"

View File

@ -175,4 +175,6 @@ var styles = StyleSheet.create({
}
});
TabBarExample.external = true;
module.exports = TabBarExample;

View File

@ -0,0 +1,90 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';
var React = require('react-native');
var {
NavigatorIOS,
StatusBarIOS,
StyleSheet,
Text,
View
} = React;
var EmptyPage = React.createClass({
render: function() {
return (
<View style={styles.emptyPage}>
<Text style={styles.emptyPageText}>
{this.props.text}
</Text>
</View>
);
},
});
var NavigatorIOSColors = React.createClass({
statics: {
title: '<NavigatorIOS> - Custom',
description: 'iOS navigation with custom nav bar colors',
},
render: function() {
// Set StatusBar with light contents to get better contrast
StatusBarIOS.setStyle(StatusBarIOS.Style['lightContent']);
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: EmptyPage,
title: '<NavigatorIOS>',
rightButtonTitle: 'Done',
onRightButtonPress: () => {
StatusBarIOS.setStyle(StatusBarIOS.Style['default']);
this.props.onExampleExit();
},
passProps: {
text: 'The nav bar has custom colors with tintColor, ' +
'barTintColor and titleTextColor props.',
},
}}
tintColor="#FFFFFF"
barTintColor="#183E63"
titleTextColor="#FFFFFF"
/>
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
},
emptyPage: {
flex: 1,
paddingTop: 64,
},
emptyPageText: {
margin: 10,
},
});
NavigatorIOSColors.external = true;
module.exports = NavigatorIOSColors;

View File

@ -131,12 +131,12 @@ exports.description = 'Monitor network status';
exports.examples = [
{
title: 'NetInfo.isConnected',
description: 'Asyncronously load and observe connectivity',
description: 'Asynchronously load and observe connectivity',
render(): ReactElement { return <IsConnected />; }
},
{
title: 'NetInfo.reachabilityIOS',
description: 'Asyncronously load and observe iOS reachability',
description: 'Asynchronously load and observe iOS reachability',
render(): ReactElement { return <ReachabilityCurrent />; }
},
{

View File

@ -22,24 +22,33 @@
{
NSURL *jsCodeLocation;
// Loading JavaScript code - uncomment the one you want.
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
// OPTION 1
// Load from development server. Start the server from the repository root:
//
// $ npm start
//
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
// iOS device are on the same Wi-Fi network.
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle?dev=true"];
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle, run
//
// $ curl http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle -o main.jsbundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
/**
* OPTION 2
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
* to your Xcode project folder and run
*
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle' -o main.jsbundle
*
* then add the `main.jsbundle` file to your project and uncomment this line:
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"UIExplorerApp"

View File

@ -26,7 +26,6 @@ var {
TouchableHighlight,
View,
} = React;
var NavigatorExample = require('./Navigator/NavigatorExample');
var { TestModule } = React.addons;
@ -39,7 +38,8 @@ var COMPONENTS = [
require('./ListViewExample'),
require('./ListViewPagingExample'),
require('./MapViewExample'),
NavigatorExample,
require('./Navigator/NavigatorExample'),
require('./NavigatorIOSColorsExample'),
require('./NavigatorIOSExample'),
require('./PickerIOSExample'),
require('./ScrollViewExample'),
@ -181,10 +181,8 @@ class UIExplorerList extends React.Component {
}
_onPressRow(example) {
if (example === NavigatorExample) {
this.props.onExternalExampleRequested(
NavigatorExample
);
if (example.external) {
this.props.onExternalExampleRequested(example);
return;
}
var Component = makeRenderable(example);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -59,11 +59,10 @@
return NO;
}
// Make sure this test runs first (underscores sort early) otherwise the
// other tests will tear out the rootView
- (void)test__RootViewLoadsAndRenders
// Make sure this test runs first because the other tests will tear out the rootView
- (void)testAAA_RootViewLoadsAndRenders
{
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
@ -72,10 +71,8 @@
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) {
if ([view respondsToSelector:@selector(attributedText)]) {
NSString *text = [(id)view attributedText].string;
if ([text isEqualToString:@"<View>"]) {
@ -120,6 +117,7 @@
[_runner runTest:_cmd module:@"TabBarExample"];
}
// Make sure this test runs last
- (void)testZZZ_NotInRecordMode
{
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");

View File

@ -198,6 +198,7 @@
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -206,6 +207,7 @@
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTActionSheet;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -213,6 +215,7 @@
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -221,6 +224,7 @@
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTActionSheet;
RUN_CLANG_STATIC_ANALYZER = NO;
SKIP_INSTALL = YES;
};
name = Release;

View File

@ -30,96 +30,97 @@ RCT_EXPORT_MODULE()
return self;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
failureCallback:(RCTResponseSenderBlock)failureCallback
successCallback:(RCTResponseSenderBlock)successCallback)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
actionSheet.title = options[@"title"];
actionSheet.title = options[@"title"];
for (NSString *option in options[@"options"]) {
[actionSheet addButtonWithTitle:option];
}
for (NSString *option in options[@"options"]) {
[actionSheet addButtonWithTitle:option];
}
if (options[@"destructiveButtonIndex"]) {
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
}
if (options[@"cancelButtonIndex"]) {
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
}
if (options[@"destructiveButtonIndex"]) {
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
}
if (options[@"cancelButtonIndex"]) {
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
}
actionSheet.delegate = self;
actionSheet.delegate = self;
_callbacks[keyForInstance(actionSheet)] = successCallback;
_callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window];
if (appWindow == nil) {
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
return;
}
[actionSheet showInView:appWindow];
});
UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window];
if (appWindow == nil) {
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
return;
}
[actionSheet showInView:appWindow];
}
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
failureCallback:(RCTResponseSenderBlock)failureCallback
successCallback:(RCTResponseSenderBlock)successCallback)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *items = [NSMutableArray array];
id message = options[@"message"];
id url = options[@"url"];
if ([message isKindOfClass:[NSString class]]) {
[items addObject:message];
}
if ([url isKindOfClass:[NSString class]]) {
[items addObject:[NSURL URLWithString:url]];
}
if ([items count] == 0) {
failureCallback(@[@"No `url` or `message` to share"]);
return;
}
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) {
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
if (activityError) {
failureCallback(@[[activityError localizedDescription]]);
} else {
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
}
};
} else {
NSMutableArray *items = [NSMutableArray array];
id message = options[@"message"];
id url = options[@"url"];
if ([message isKindOfClass:[NSString class]]) {
[items addObject:message];
}
if ([url isKindOfClass:[NSString class]]) {
[items addObject:[NSURL URLWithString:url]];
}
if ([items count] == 0) {
failureCallback(@[@"No `url` or `message` to share"]);
return;
}
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) {
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
if (activityError) {
failureCallback(@[[activityError localizedDescription]]);
} else {
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
}
};
} else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) {
// Legacy iOS 7 implementation
share.completionHandler = ^(NSString *activityType, BOOL completed) {
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
};
} else
if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) {
// Legacy iOS 7 implementation
share.completionHandler = ^(NSString *activityType, BOOL completed) {
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
};
} else
#endif
{
// iOS 8 version
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
};
}
{
// iOS 8 version
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
};
}
[ctrl presentViewController:share animated:YES completion:nil];
});
}
[ctrl presentViewController:share animated:YES completion:nil];
}
#pragma mark UIActionSheetDelegate Methods
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *key = keyForInstance(actionSheet);
NSString *key = RCTKeyForInstance(actionSheet);
RCTResponseSenderBlock callback = _callbacks[key];
if (callback) {
callback(@[@(buttonIndex)]);
@ -133,7 +134,7 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
#pragma mark Private
NS_INLINE NSString *keyForInstance(id instance)
static NSString *RCTKeyForInstance(id instance)
{
return [NSString stringWithFormat:@"%p", instance];
}

View File

@ -208,6 +208,7 @@
832C81951AAF6DF0007FA2F7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -215,6 +216,7 @@
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -222,6 +224,7 @@
832C81961AAF6DF0007FA2F7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -20,6 +20,9 @@
type EasingFunction = (t: number) => number;
var defaults = {
linear: function(t: number): number {
return t;
},
easeInQuad: function(t: number): number {
return t * t;
},

View File

@ -68,6 +68,11 @@ RCT_EXPORT_MODULE()
return self;
}
- (dispatch_queue_t)methodQueue
{
return _bridge.uiManager.methodQueue;
}
- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName
{
if (count == 1) {
@ -259,7 +264,9 @@ RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag)
RCTAnimationExperimentalManager *strongSelf = weakSelf;
NSNumber *reactTag = strongSelf->_animationRegistry[animationTag];
if (!reactTag) return;
if (!reactTag) {
return;
}
UIView *view = viewRegistry[reactTag];
for (NSString *animationKey in view.layer.animationKeys) {

View File

@ -0,0 +1,44 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*/
'use strict';
var NativeModules = {
I18n: {
translationsDictionary: {
'Good bye, {name}!|Bye message': '¡Adiós {name}!',
},
},
Timing: {
createTimer: jest.genMockFunction(),
deleteTimer: jest.genMockFunction(),
},
GraphPhotoUpload: {
upload: jest.genMockFunction(),
},
FacebookSDK: {
login: jest.genMockFunction(),
logout: jest.genMockFunction(),
queryGraphPath: jest.genMockFunction().mockImpl(
(path, method, params, callback) => callback()
),
},
DataManager: {
queryData: jest.genMockFunction(),
},
UIManager: {
customBubblingEventTypes: {},
customDirectEventTypes: {},
},
AsyncLocalStorage: {
getItem: jest.genMockFunction(),
setItem: jest.genMockFunction(),
removeItem: jest.genMockFunction(),
clear: jest.genMockFunction(),
},
SourceCode: {
scriptURL: null,
},
};
module.exports = NativeModules;

View File

@ -15,13 +15,12 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheet = require('StyleSheet');
var View = require('View');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var keyMirror = require('keyMirror');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var verifyPropTypes = require('verifyPropTypes');
var SpinnerSize = keyMirror({
large: null,
@ -100,14 +99,17 @@ var styles = StyleSheet.create({
}
});
var UIActivityIndicatorView = createReactIOSNativeComponentClass({
validAttributes: merge(
ReactIOSViewAttributes.UIView, {
activityIndicatorViewStyle: true, // UIActivityIndicatorViewStyle=UIActivityIndicatorViewStyleWhite
animating: true,
color: true,
}),
uiViewClassName: 'UIActivityIndicatorView',
});
var UIActivityIndicatorView = requireNativeComponent(
'UIActivityIndicatorView',
null
);
if (__DEV__) {
var nativeOnlyProps = {activityIndicatorViewStyle: true};
verifyPropTypes(
ActivityIndicatorIOS,
UIActivityIndicatorView.viewConfig,
nativeOnlyProps
);
}
module.exports = ActivityIndicatorIOS;

View File

@ -16,14 +16,11 @@
var NativeMethodsMixin = require('NativeMethodsMixin');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var RCTDatePickerIOSConsts = require('NativeModules').UIManager.RCTDatePicker.Constants;
var StyleSheet = require('StyleSheet');
var View = require('View');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var DATEPICKER = 'datepicker';
@ -148,18 +145,6 @@ var styles = StyleSheet.create({
},
});
var rkDatePickerIOSAttributes = merge(ReactIOSViewAttributes.UIView, {
date: true,
maximumDate: true,
minimumDate: true,
mode: true,
minuteInterval: true,
timeZoneOffsetInMinutes: true,
});
var RCTDatePickerIOS = createReactIOSNativeComponentClass({
validAttributes: rkDatePickerIOSAttributes,
uiViewClassName: 'RCTDatePicker',
});
var RCTDatePickerIOS = requireNativeComponent('RCTDatePicker', DatePickerIOS);
module.exports = DatePickerIOS;

View File

@ -13,6 +13,7 @@
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var View = require('View');
@ -21,8 +22,15 @@ var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentC
var deepDiffer = require('deepDiffer');
var insetsDiffer = require('insetsDiffer');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
type Event = Object;
type MapRegion = {
latitude: number;
longitude: number;
latitudeDelta: number;
longitudeDelta: number;
};
var MapView = React.createClass({
mixins: [NativeMethodsMixin],
@ -150,46 +158,30 @@ var MapView = React.createClass({
},
render: function() {
return (
<RCTMap
style={this.props.style}
showsUserLocation={this.props.showsUserLocation}
zoomEnabled={this.props.zoomEnabled}
rotateEnabled={this.props.rotateEnabled}
pitchEnabled={this.props.pitchEnabled}
scrollEnabled={this.props.scrollEnabled}
region={this.props.region}
annotations={this.props.annotations}
maxDelta={this.props.maxDelta}
minDelta={this.props.minDelta}
legalLabelInsets={this.props.legalLabelInsets}
onChange={this._onChange}
onTouchStart={this.props.onTouchStart}
onTouchMove={this.props.onTouchMove}
onTouchEnd={this.props.onTouchEnd}
onTouchCancel={this.props.onTouchCancel}
/>
);
return <RCTMap {...this.props} onChange={this._onChange} />;
},
});
var RCTMap = createReactIOSNativeComponentClass({
validAttributes: merge(
ReactIOSViewAttributes.UIView, {
showsUserLocation: true,
zoomEnabled: true,
rotateEnabled: true,
pitchEnabled: true,
scrollEnabled: true,
region: {diff: deepDiffer},
annotations: {diff: deepDiffer},
maxDelta: true,
minDelta: true,
legalLabelInsets: {diff: insetsDiffer},
}
),
uiViewClassName: 'RCTMap',
});
if (Platform.OS === 'android') {
var RCTMap = createReactIOSNativeComponentClass({
validAttributes: merge(
ReactIOSViewAttributes.UIView, {
showsUserLocation: true,
zoomEnabled: true,
rotateEnabled: true,
pitchEnabled: true,
scrollEnabled: true,
region: {diff: deepDiffer},
annotations: {diff: deepDiffer},
maxDelta: true,
minDelta: true,
legalLabelInsets: {diff: insetsDiffer},
}
),
uiViewClassName: 'RCTMap',
});
} else {
var RCTMap = requireNativeComponent('RCTMap', MapView);
}
module.exports = MapView;

View File

@ -252,6 +252,16 @@ var NavigatorIOS = React.createClass({
*/
tintColor: PropTypes.string,
/**
* The background color of the navigation bar
*/
barTintColor: PropTypes.string,
/**
* The text color of the navigation bar title
*/
titleTextColor: PropTypes.string,
},
navigator: (undefined: ?Object),
@ -554,7 +564,9 @@ var NavigatorIOS = React.createClass({
rightButtonTitle={route.rightButtonTitle}
onNavRightButtonTap={route.onRightButtonPress}
navigationBarHidden={this.props.navigationBarHidden}
tintColor={this.props.tintColor}>
tintColor={this.props.tintColor}
barTintColor={this.props.barTintColor}
titleTextColor={this.props.titleTextColor}>
<Component
navigator={this.navigator}
route={route}

View File

@ -371,10 +371,6 @@ if (Platform.OS === 'android') {
uiViewClassName: 'AndroidHorizontalScrollView',
});
} else if (Platform.OS === 'ios') {
var RCTScrollView = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'RCTScrollView',
});
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
}

View File

@ -12,16 +12,11 @@
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheet = require('StyleSheet');
var View = require('View');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
type Event = Object;
@ -56,6 +51,18 @@ var SliderIOS = React.createClass({
*/
maximumValue: PropTypes.number,
/**
* The color used for the track to the left of the button. Overrides the
* default blue gradient image.
*/
minimumTrackTintColor: PropTypes.string,
/**
* The color used for the track to the right of the button. Overrides the
* default blue gradient image.
*/
maximumTrackTintColor: PropTypes.string,
/**
* Callback continuously called while the user is dragging the slider.
*/
@ -86,6 +93,8 @@ var SliderIOS = React.createClass({
value={this.props.value}
maximumValue={this.props.maximumValue}
minimumValue={this.props.minimumValue}
minimumTrackTintColor={this.props.minimumTrackTintColor}
maximumTrackTintColor={this.props.maximumTrackTintColor}
onChange={this._onValueChange}
/>
);
@ -98,20 +107,6 @@ var styles = StyleSheet.create({
},
});
if (Platform.OS === 'ios') {
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
} else {
var validAttributes = {
...ReactIOSViewAttributes.UIView,
value: true,
minimumValue: true,
maximumValue: true,
};
var RCTSlider = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'RCTSlider',
});
}
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
module.exports = SliderIOS;

View File

@ -16,11 +16,9 @@
var NativeMethodsMixin = require('NativeMethodsMixin');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheet = require('StyleSheet');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var SWITCH = 'switch';
@ -88,20 +86,16 @@ var SwitchIOS = React.createClass({
// The underlying switch might have changed, but we're controlled,
// and so want to ensure it represents our value.
this.refs[SWITCH].setNativeProps({on: this.props.value});
this.refs[SWITCH].setNativeProps({value: this.props.value});
},
render: function() {
return (
<RCTSwitch
{...this.props}
ref={SWITCH}
style={[styles.rkSwitch, this.props.style]}
enabled={!this.props.disabled}
on={this.props.value}
onChange={this._onChange}
onTintColor={this.props.onTintColor}
thumbTintColor={this.props.thumbTintColor}
tintColor={this.props.tintColor}
style={[styles.rkSwitch, this.props.style]}
/>
);
}
@ -114,17 +108,6 @@ var styles = StyleSheet.create({
},
});
var rkSwitchAttributes = merge(ReactIOSViewAttributes.UIView, {
onTintColor: true,
tintColor: true,
thumbTintColor: true,
on: true,
enabled: true,
});
var RCTSwitch = createReactIOSNativeComponentClass({
validAttributes: rkSwitchAttributes,
uiViewClassName: 'RCTSwitch',
});
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
module.exports = SwitchIOS;

View File

@ -12,12 +12,11 @@
'use strict';
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheet = require('StyleSheet');
var TabBarItemIOS = require('TabBarItemIOS');
var View = require('View');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var requireNativeComponent = require('requireNativeComponent');
var TabBarIOS = React.createClass({
statics: {
@ -43,10 +42,6 @@ var styles = StyleSheet.create({
}
});
var config = {
validAttributes: ReactIOSViewAttributes.UIView,
uiViewClassName: 'RCTTabBar',
};
var RCTTabBar = createReactIOSNativeComponentClass(config);
var RCTTabBar = requireNativeComponent('RCTTabBar', TabBarIOS);
module.exports = TabBarIOS;

View File

@ -13,13 +13,11 @@
var Image = require('Image');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
var View = require('View');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var TabBarItemIOS = React.createClass({
propTypes: {
@ -121,7 +119,7 @@ var TabBarItemIOS = React.createClass({
selectedIcon={this.props.selectedIcon && this.props.selectedIcon.uri}
onPress={this.props.onPress}
selected={this.props.selected}
badgeValue={badge}
badge={badge}
title={this.props.title}
style={[styles.tab, this.props.style]}>
{tabContents}
@ -140,15 +138,6 @@ var styles = StyleSheet.create({
}
});
var RCTTabBarItem = createReactIOSNativeComponentClass({
validAttributes: merge(ReactIOSViewAttributes.UIView, {
title: true,
icon: true,
selectedIcon: true,
selected: true,
badgeValue: true,
}),
uiViewClassName: 'RCTTabBarItem',
});
var RCTTabBarItem = requireNativeComponent('RCTTabBarItem', TabBarItemIOS);
module.exports = TabBarItemIOS;

View File

@ -350,6 +350,9 @@ var TextInput = React.createClass({
componentWillUnmount: function() {
this._focusSubscription && this._focusSubscription.remove();
if (this.isFocused()) {
this.blur();
}
},
_bufferTimeout: (undefined: ?number),

View File

@ -13,12 +13,13 @@
var NativeMethodsMixin = require('NativeMethodsMixin');
var PropTypes = require('ReactPropTypes');
var RCTUIManager = require('NativeModules').UIManager;
var React = require('React');
var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheetPropType = require('StyleSheetPropType');
var ViewStylePropTypes = require('ViewStylePropTypes');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var stylePropType = StyleSheetPropType(ViewStylePropTypes);
@ -136,6 +137,20 @@ var View = React.createClass({
* (or one of its superviews).
*/
removeClippedSubviews: PropTypes.bool,
/**
* Whether this view should render itself (and all of its children) into a
* single hardware texture on the GPU.
*
* On Android, this is useful for animations and interactions that only
* modify opacity, rotation, translation, and/or scale: in those cases, the
* view doesn't have to be redrawn and display lists don't need to be
* re-executed. The texture can just be re-used and re-composited with
* different parameters. The downside is that this can use up limited video
* memory, so this prop should be set back to false at the end of the
* interaction/animation.
*/
renderToHardwareTextureAndroid: PropTypes.bool,
},
render: function() {
@ -143,17 +158,26 @@ var View = React.createClass({
},
});
var RCTView = createReactIOSNativeComponentClass({
validAttributes: ReactIOSViewAttributes.RCTView,
uiViewClassName: 'RCTView',
});
RCTView.propTypes = View.propTypes;
if (__DEV__) {
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {};
for (var prop in viewConfig.nativeProps) {
var viewAny: any = View; // Appease flow
if (!viewAny.propTypes[prop] && !ReactIOSStyleAttributes[prop]) {
throw new Error(
'View is missing propType for native prop `' + prop + '`'
);
}
}
}
var ViewToExport = RCTView;
if (__DEV__) {
ViewToExport = View;
}
module.exports = ViewToExport;

View File

@ -34,7 +34,10 @@ var ViewStylePropTypes = {
),
shadowOpacity: ReactPropTypes.number,
shadowRadius: ReactPropTypes.number,
transform: ReactPropTypes.arrayOf(ReactPropTypes.object),
transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number),
// DEPRECATED
rotation: ReactPropTypes.number,
scaleX: ReactPropTypes.number,
scaleY: ReactPropTypes.number,

View File

@ -19,16 +19,13 @@ var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
var insetsDiffer = require('insetsDiffer');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var PropTypes = React.PropTypes;
var RCTWebViewManager = require('NativeModules').WebViewManager;
var invariant = require('invariant');
var BGWASH = 'rgba(255,255,255,0.8)';
var RCT_WEBVIEW_REF = 'webview';
@ -87,6 +84,8 @@ var WebView = React.createClass({
html: PropTypes.string,
renderError: PropTypes.func, // view to show if there's an error
renderLoading: PropTypes.func, // loading indicator to show
bounces: PropTypes.bool,
scrollEnabled: PropTypes.bool,
automaticallyAdjustContentInsets: PropTypes.bool,
shouldInjectAJAXHandler: PropTypes.bool,
contentInset: EdgeInsetsPropType,
@ -131,7 +130,7 @@ var WebView = React.createClass({
);
}
var webViewStyles = [styles.container, this.props.style];
var webViewStyles = [styles.container, styles.webView, this.props.style];
if (this.state.viewState === WebViewState.LOADING ||
this.state.viewState === WebViewState.ERROR) {
// if we're in either LOADING or ERROR states, don't show the webView
@ -145,6 +144,8 @@ var WebView = React.createClass({
style={webViewStyles}
url={this.props.url}
html={this.props.html}
bounces={this.props.bounces}
scrollEnabled={this.props.scrollEnabled}
shouldInjectAJAXHandler={this.props.shouldInjectAJAXHandler}
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
@ -209,16 +210,7 @@ var WebView = React.createClass({
},
});
var RCTWebView = createReactIOSNativeComponentClass({
validAttributes: merge(ReactIOSViewAttributes.UIView, {
url: true,
html: true,
contentInset: {diff: insetsDiffer},
automaticallyAdjustContentInsets: true,
shouldInjectAJAXHandler: true
}),
uiViewClassName: 'RCTWebView',
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
var styles = StyleSheet.create({
container: {
@ -250,6 +242,9 @@ var styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
webView: {
backgroundColor: '#ffffff',
}
});
module.exports = WebView;

View File

@ -135,11 +135,12 @@ var GESTURE_ACTIONS = [
* />
* ```
*
* ### Navigation Methods
* ### Navigator Methods
*
* `Navigator` can be told to navigate in two ways. If you have a ref to
* the element, you can invoke several methods on it to trigger navigation:
* If you have a ref to the Navigator element, you can invoke several methods
* on it to trigger navigation:
*
* - `getCurrentRoutes()` - returns the current list of routes
* - `jumpBack()` - Jump backward without unmounting the current scene
* - `jumpForward()` - Jump forward to the next scene in the route stack
* - `jumpTo(route)` - Transition to an existing scene without unmounting
@ -156,18 +157,39 @@ var GESTURE_ACTIONS = [
* - `popToTop()` - Pop to the first scene in the stack, unmounting every
* other scene
*
* ### Navigator Object
* ### Navigation Context
*
* The navigator object is made available to scenes through the `renderScene`
* function. The object has all of the navigation methods on it, as well as a
* few utilities:
* The navigator context object is made available to scenes through the
* `renderScene` function. Alternatively, any scene or component inside a
* Navigator can get the navigation context by calling
* `Navigator.getContext(this)`.
*
* - `parentNavigator` - a refrence to the parent navigator object that was
* passed in through props.navigator
* - `onWillFocus` - used to pass a navigation focus event up to the parent
* navigator
* - `onDidFocus` - used to pass a navigation focus event up to the parent
* navigator
* Unlike the Navigator methods, the functions in navigation context do not
* directly control a specific navigator. Instead, the navigator context allows
* a scene to request navigation from its parents. Navigation requests will
* travel up through the hierarchy of Navigators, and will be resolved by the
* deepest active navigator.
*
* Navigation context objects contain the following:
*
* - `getCurrentRoutes()` - returns the routes for the closest navigator
* - `jumpBack()` - Jump backward without unmounting the current scene
* - `jumpForward()` - Jump forward to the next scene in the route stack
* - `jumpTo(route)` - Transition to an existing scene without unmounting
* - `parentNavigator` - a refrence to the parent navigation context
* - `push(route)` - Navigate forward to a new scene, squashing any scenes
* that you could `jumpForward` to
* - `pop()` - Transition back and unmount the current scene
* - `replace(route)` - Replace the current scene with a new route
* - `replaceAtIndex(route, index)` - Replace a scene as specified by an index
* - `replacePrevious(route)` - Replace the previous scene
* - `route` - The route that was used to render the scene with this context
* - `immediatelyResetRouteStack(routeStack)` - Reset every scene with an
* array of routes
* - `popToRoute(route)` - Pop to a particular scene, as specified by it's
* route. All scenes after it will be unmounted
* - `popToTop()` - Pop to the first scene in the stack, unmounting every
* other scene
*
*/
var Navigator = React.createClass({
@ -306,25 +328,30 @@ var Navigator = React.createClass({
this.parentNavigator = getNavigatorContext(this) || this.props.navigator;
this._subRouteFocus = [];
this.navigatorContext = {
// Actions for child navigators or interceptors:
setHandlerForRoute: this.setHandlerForRoute,
request: this.request,
// Contextual utilities
parentNavigator: this.parentNavigator,
getCurrentRoutes: this.getCurrentRoutes,
// `route` is injected by NavigatorStaticContextContainer
// Legacy, imperitive nav actions. Use request when possible.
// Contextual nav actions
pop: this.requestPop,
popToRoute: this.requestPopTo,
// Legacy, imperitive nav actions. Will transition these to contextual actions
jumpBack: this.jumpBack,
jumpForward: this.jumpForward,
jumpTo: this.jumpTo,
push: this.push,
pop: this.pop,
replace: this.replace,
replaceAtIndex: this.replaceAtIndex,
replacePrevious: this.replacePrevious,
replacePreviousAndPop: this.replacePreviousAndPop,
immediatelyResetRouteStack: this.immediatelyResetRouteStack,
resetTo: this.resetTo,
popToRoute: this.popToRoute,
popToTop: this.popToTop,
};
this._handlers = {};
@ -349,6 +376,14 @@ var Navigator = React.createClass({
return this._handleRequest.apply(null, arguments);
},
requestPop: function() {
return this.request('pop');
},
requestPopTo: function(route) {
return this.request('pop', route);
},
_handleRequest: function(action, arg1, arg2) {
var childHandler = this._handlers[this.state.presentedIndex];
if (childHandler && childHandler(action, arg1, arg2)) {
@ -356,7 +391,7 @@ var Navigator = React.createClass({
}
switch (action) {
case 'pop':
return this._handlePop();
return this._handlePop(arg1);
case 'push':
return this._handlePush(arg1);
default:
@ -365,11 +400,20 @@ var Navigator = React.createClass({
}
},
_handlePop: function() {
_handlePop: function(route) {
if (route) {
var hasRoute = this.state.routeStack.indexOf(route) !== -1;
if (hasRoute) {
this.popToRoute(route);
return true;
} else {
return false;
}
}
if (this.state.presentedIndex === 0) {
return false;
}
this._popN(1);
this.pop();
return true;
},
@ -415,7 +459,7 @@ var Navigator = React.createClass({
},
_handleAndroidBackPress: function() {
var didPop = this.pop();
var didPop = this.requestPop();
if (!didPop) {
BackAndroid.exitApp();
}
@ -494,6 +538,7 @@ var Navigator = React.createClass({
_completeTransition: function() {
if (this.spring.getCurrentValue() === 1) {
this._onAnimationEnd();
var presentedIndex = this.state.toIndex;
this.state.presentedIndex = presentedIndex;
this.state.fromIndex = presentedIndex;
@ -515,6 +560,7 @@ var Navigator = React.createClass({
// For visual consistency, the from index is always used to configure the spring
this.state.sceneConfigStack[this.state.fromIndex]
);
this._onAnimationStart();
this.state.isAnimating = true;
this.spring.setVelocity(v);
this.spring.setEndValue(1);
@ -573,6 +619,34 @@ var Navigator = React.createClass({
}
},
_onAnimationStart: function() {
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, true);
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, true);
var navBar = this._navBar;
if (navBar && navBar.onAnimationStart) {
navBar.onAnimationStart(this.state.fromIndex, this.state.toIndex);
}
},
_onAnimationEnd: function() {
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, false);
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, false);
var navBar = this._navBar;
if (navBar && navBar.onAnimationEnd) {
navBar.onAnimationEnd(this.state.fromIndex, this.state.toIndex);
}
},
_setRenderSceneToHarwareTextureAndroid: function(sceneIndex, shouldRenderToHardwareTexture) {
var viewAtIndex = this.refs['scene_' + sceneIndex];
if (viewAtIndex === null || viewAtIndex === undefined) {
return;
}
viewAtIndex.setNativeProps({renderToHardwareTextureAndroid: shouldRenderToHardwareTexture});
},
/**
* Becomes the responder on touch start (capture) while animating so that it
* blocks all touch interactions inside of it. However, this responder lock
@ -610,6 +684,7 @@ var Navigator = React.createClass({
this.state.fromIndex = this.state.presentedIndex;
var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction);
this.state.toIndex = this.state.presentedIndex + gestureSceneDelta;
this._onAnimationStart();
}
},
@ -873,7 +948,7 @@ var Navigator = React.createClass({
},
pop: function() {
return this.request('pop');
this._popN(1);
},
/**
@ -1004,8 +1079,7 @@ var Navigator = React.createClass({
// To avoid visual glitches, we never re-render scenes during a transition.
// We assume that `state.updatingRangeLength` will have a length during the
// initial render of any scene
var shouldRenderScenes = !this.state.isAnimating &&
this.state.updatingRangeLength !== 0;
var shouldRenderScenes = this.state.updatingRangeLength !== 0;
if (shouldRenderScenes) {
return (
<StaticContainer shouldUpdate={true}>

View File

@ -138,6 +138,37 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
}
},
onAnimationStart: function(fromIndex, toIndex) {
var max = Math.max(fromIndex, toIndex);
var min = Math.min(fromIndex, toIndex);
for (var index = min; index <= max; index++) {
this._setRenderViewsToHardwareTextureAndroid(index, true);
}
},
onAnimationEnd: function(fromIndex, toIndex) {
var max = Math.max(fromIndex, toIndex);
var min = Math.min(fromIndex, toIndex);
for (var index = min; index <= max; index++) {
this._setRenderViewsToHardwareTextureAndroid(index, false);
}
},
_setRenderViewsToHardwareTextureAndroid: function(index, renderToHardwareTexture) {
var props = {
renderToHardwareTextureAndroid: renderToHardwareTexture,
};
this.refs['crumb_' + index].setNativeProps(props);
this.refs['icon_' + index].setNativeProps(props);
this.refs['separator_' + index].setNativeProps(props);
this.refs['title_' + index].setNativeProps(props);
var right = this.refs['right_' + index];
if (right) {
right.setNativeProps(props);
}
},
render: function() {
var navState = this.props.navState;
var icons = navState && navState.routeStack.map(this._renderOrReturnBreadcrumb);
@ -260,7 +291,7 @@ var styles = StyleSheet.create({
height: NavigatorNavigationBarStyles.General.TotalNavHeight,
top: 0,
left: 0,
width: NavigatorNavigationBarStyles.General.ScreenWidth,
right: 0,
},
});

View File

@ -26,12 +26,13 @@
*/
'use strict';
var Dimensions = require('Dimensions');
var NavigatorNavigationBarStyles = require('NavigatorNavigationBarStyles');
var buildStyleInterpolator = require('buildStyleInterpolator');
var merge = require('merge');
var SCREEN_WIDTH = NavigatorNavigationBarStyles.General.ScreenWidth;
var SCREEN_WIDTH = Dimensions.get('window').width;
var STATUS_BAR_HEIGHT = NavigatorNavigationBarStyles.General.StatusBarHeight;
var NAV_BAR_HEIGHT = NavigatorNavigationBarStyles.General.NavBarHeight;
@ -39,7 +40,6 @@ var SPACING = 4;
var ICON_WIDTH = 40;
var SEPARATOR_WIDTH = 9;
var CRUMB_WIDTH = ICON_WIDTH + SEPARATOR_WIDTH;
var RIGHT_BUTTON_WIDTH = 58;
var OPACITY_RATIO = 100;
var ICON_INACTIVE_OPACITY = 0.6;
@ -74,18 +74,17 @@ var TITLE_BASE = {
// For first title styles, make sure first title is centered
var FIRST_TITLE_BASE = merge(TITLE_BASE, {
left: 0,
right: 0,
alignItems: 'center',
width: SCREEN_WIDTH,
height: NAV_BAR_HEIGHT,
});
var RIGHT_BUTTON_BASE = {
position: 'absolute',
top: STATUS_BAR_HEIGHT,
left: SCREEN_WIDTH - SPACING - RIGHT_BUTTON_WIDTH,
right: SPACING,
overflow: 'hidden',
opacity: 1,
width: RIGHT_BUTTON_WIDTH,
height: NAV_BAR_HEIGHT,
backgroundColor: 'transparent',
};

View File

@ -192,7 +192,7 @@ var styles = StyleSheet.create({
height: NavigatorNavigationBarStyles.General.TotalNavHeight,
top: 0,
left: 0,
width: NavigatorNavigationBarStyles.General.ScreenWidth,
right: 0,
backgroundColor: 'transparent',
},
});

View File

@ -169,7 +169,6 @@ module.exports = {
NavBarHeight: NAV_BAR_HEIGHT,
StatusBarHeight: STATUS_BAR_HEIGHT,
TotalNavHeight: NAV_HEIGHT,
ScreenWidth: SCREEN_WIDTH,
},
Interpolators,
Stages,

View File

@ -198,6 +198,7 @@
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -206,6 +207,7 @@
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTGeolocation;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -213,6 +215,7 @@
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -33,7 +33,9 @@ typedef struct {
CLLocationAccuracy accuracy;
} RCTLocationOptions;
static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
@implementation RCTConvert (RCTLocationOptions)
+ (RCTLocationOptions)RCTLocationOptions:(id)json
{
NSDictionary *options = [RCTConvert NSDictionary:json];
return (RCTLocationOptions){
@ -43,6 +45,8 @@ static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
};
}
@end
static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */)
{
if (!msg) {
@ -121,6 +125,12 @@ RCT_EXPORT_MODULE()
- (void)dealloc
{
[_locationManager stopUpdatingLocation];
_locationManager.delegate = nil;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
#pragma mark - Private API
@ -157,34 +167,26 @@ RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON)
{
[self checkLocationConfig];
dispatch_async(dispatch_get_main_queue(), ^{
// Select best options
_observerOptions = [RCTConvert RCTLocationOptions:optionsJSON];
for (RCTLocationRequest *request in _pendingRequests) {
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
}
// Select best options
_observerOptions = RCTLocationOptionsWithJSON(optionsJSON);
for (RCTLocationRequest *request in _pendingRequests) {
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
}
_locationManager.desiredAccuracy = _observerOptions.accuracy;
[self beginLocationUpdates];
_observingLocation = YES;
});
_locationManager.desiredAccuracy = _observerOptions.accuracy;
[self beginLocationUpdates];
_observingLocation = YES;
}
RCT_EXPORT_METHOD(stopObserving)
{
dispatch_async(dispatch_get_main_queue(), ^{
// Stop observing
_observingLocation = NO;
// Stop observing
_observingLocation = NO;
// Stop updating if no pending requests
if (_pendingRequests.count == 0) {
[_locationManager stopUpdatingLocation];
}
});
// Stop updating if no pending requests
if (_pendingRequests.count == 0) {
[_locationManager stopUpdatingLocation];
}
}
RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
@ -198,56 +200,50 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (![CLLocationManager locationServicesEnabled]) {
if (errorBlock) {
errorBlock(@[
RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
]);
return;
}
}
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
if (errorBlock) {
errorBlock(@[
RCTPositionError(RCTPositionErrorDenied, nil)
]);
return;
}
}
// Get options
RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON);
// Check if previous recorded location exists and is good enough
if (_lastLocationEvent &&
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
// Call success block with most recent known location
successBlock(@[_lastLocationEvent]);
if (![CLLocationManager locationServicesEnabled]) {
if (errorBlock) {
errorBlock(@[
RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
]);
return;
}
}
// Create request
RCTLocationRequest *request = [[RCTLocationRequest alloc] init];
request.successBlock = successBlock;
request.errorBlock = errorBlock ?: ^(NSArray *args){};
request.options = options;
request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout
target:self
selector:@selector(timeout:)
userInfo:request
repeats:NO];
[_pendingRequests addObject:request];
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
if (errorBlock) {
errorBlock(@[
RCTPositionError(RCTPositionErrorDenied, nil)
]);
return;
}
}
// Configure location manager and begin updating location
_locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
[self beginLocationUpdates];
// Check if previous recorded location exists and is good enough
RCTLocationOptions options = [RCTConvert RCTLocationOptions:optionsJSON];
if (_lastLocationEvent &&
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
});
// Call success block with most recent known location
successBlock(@[_lastLocationEvent]);
return;
}
// Create request
RCTLocationRequest *request = [[RCTLocationRequest alloc] init];
request.successBlock = successBlock;
request.errorBlock = errorBlock ?: ^(NSArray *args){};
request.options = options;
request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout
target:self
selector:@selector(timeout:)
userInfo:request
repeats:NO];
[_pendingRequests addObject:request];
// Configure location manager and begin updating location
_locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
[self beginLocationUpdates];
}
#pragma mark - CLLocationManagerDelegate

View File

@ -12,25 +12,23 @@
'use strict';
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheet = require('StyleSheet');
var StyleSheetPropType = require('StyleSheetPropType');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var warning = require('warning');
var resolveAssetSource = require('resolveAssetSource');
var verifyPropTypes = require('verifyPropTypes');
var warning = require('warning');
/**
* A react component for displaying different types of images,
@ -125,10 +123,13 @@ var Image = React.createClass({
'not be set directly on Image.');
}
}
var style = flattenStyle([styles.base, this.props.style]);
invariant(style, "style must be initialized");
var source = this.props.source;
invariant(source, "source must be initialized");
var source = resolveAssetSource(this.props.source);
invariant(source, 'source must be initialized');
var {width, height} = source;
var style = flattenStyle([{width, height}, styles.base, this.props.style]);
invariant(style, 'style must be initialized');
var isNetwork = source.uri && source.uri.match(/^https?:/);
invariant(
!(isNetwork && source.isStatic),
@ -156,12 +157,6 @@ var Image = React.createClass({
contentMode,
tintColor: style.tintColor,
});
if (Platform.OS === 'android') {
// TODO: update android native code to not need this
nativeProps.resizeMode = contentMode;
delete nativeProps.contentMode;
}
if (isStored) {
nativeProps.imageTag = source.uri;
} else {
@ -180,30 +175,9 @@ var styles = StyleSheet.create({
},
});
if (Platform.OS === 'android') {
var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
accessible: true,
accessibilityLabel: true,
capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
imageTag: true,
resizeMode: true,
src: true,
testID: PropTypes.string,
});
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
var RCTStaticImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
uiViewClassName: 'RCTStaticImage',
});
var RCTNetworkImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
uiViewClassName: 'RCTNetworkImageView',
});
} else {
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
}
var nativeOnlyProps = {
src: true,
defaultImageSrc: true,

View File

@ -30,8 +30,8 @@ var ImageResizeMode = keyMirror({
cover: null,
/**
* stretch - The image will be stretched to fill the entire frame of the
* view without clipping. This may change the aspect ratio of the image,
* distoring it. Only supported on iOS.
* view without clipping. This may change the aspect ratio of the image,
* distoring it.
*/
stretch: null,
});

View File

@ -14,7 +14,6 @@
static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSource)
{
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
CFRelease(imageSource);
return nil;
}

View File

@ -240,6 +240,7 @@
58B511721A9E6B3D00147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -251,6 +252,7 @@
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTImage;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -258,6 +260,7 @@
58B511731A9E6B3D00147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -269,6 +272,7 @@
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTImage;
RUN_CLANG_STATIC_ANALYZER = NO;
SKIP_INSTALL = YES;
};
name = Release;

View File

@ -0,0 +1,135 @@
/**
* 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.
*/
'use strict';
jest.dontMock('../resolveAssetSource');
var resolveAssetSource;
var SourceCode;
function expectResolvesAsset(input, expectedSource) {
expect(resolveAssetSource(input)).toEqual(expectedSource);
}
describe('resolveAssetSource', () => {
beforeEach(() => {
jest.resetModuleRegistry();
SourceCode = require('NativeModules').SourceCode;
resolveAssetSource = require('../resolveAssetSource');
});
it('returns same source for simple static and network images', () => {
var source1 = {uri: 'https://www.facebook.com/logo'};
expect(resolveAssetSource(source1)).toBe(source1);
var source2 = {isStatic: true, uri: 'logo'};
expect(resolveAssetSource(source2)).toBe(source2);
});
describe('bundle was loaded from network', () => {
beforeEach(() => {
SourceCode.scriptURL = 'http://10.0.0.1:8081/main.bundle';
});
it('uses network image', () => {
expectResolvesAsset({
__packager_asset: true,
fileSystemLocation: '/root/app/module/a',
httpServerLocation: '/assets/module/a',
width: 100,
height: 200,
scales: [1],
hash: '5b6f00f',
name: 'logo',
type: 'png',
}, {
isStatic: false,
width: 100,
height: 200,
uri: 'http://10.0.0.1:8081/assets/module/a/logo.png?hash=5b6f00f',
});
});
it('picks matching scale', () => {
expectResolvesAsset({
__packager_asset: true,
fileSystemLocation: '/root/app/module/a',
httpServerLocation: '/assets/module/a',
width: 100,
height: 200,
scales: [1, 2, 3],
hash: '5b6f00f',
name: 'logo',
type: 'png',
}, {
isStatic: false,
width: 100,
height: 200,
uri: 'http://10.0.0.1:8081/assets/module/a/logo@2x.png?hash=5b6f00f',
});
});
it('does not change deprecated assets', () => {
expectResolvesAsset({
__packager_asset: true,
deprecated: true,
fileSystemLocation: '/root/app/module/a',
httpServerLocation: '/assets/module/a',
width: 100,
height: 200,
scales: [1],
hash: '5b6f00f',
name: 'logo',
type: 'png',
}, {
isStatic: true,
width: 100,
height: 200,
uri: 'logo',
});
});
});
describe('bundle was loaded from file', () => {
beforeEach(() => {
SourceCode.scriptURL = 'file:///Path/To/Simulator/main.bundle';
});
it('uses pre-packed image', () => {
expectResolvesAsset({
__packager_asset: true,
fileSystemLocation: '/root/app/module/a',
httpServerLocation: '/assets/module/a',
width: 100,
height: 200,
scales: [1],
hash: '5b6f00f',
name: 'logo',
type: 'png',
}, {
isStatic: true,
width: 100,
height: 200,
uri: 'assets/module/a/logo.png',
});
});
});
});
describe('resolveAssetSource.pickScale', () => {
it('picks matching scale', () => {
expect(resolveAssetSource.pickScale([1], 2)).toBe(1);
expect(resolveAssetSource.pickScale([1, 2, 3], 2)).toBe(2);
expect(resolveAssetSource.pickScale([1, 2], 3)).toBe(2);
expect(resolveAssetSource.pickScale([1, 2, 3, 4], 3.5)).toBe(4);
expect(resolveAssetSource.pickScale([3, 4], 2)).toBe(3);
expect(resolveAssetSource.pickScale([], 2)).toBe(1);
});
});

View File

@ -0,0 +1,101 @@
/**
* 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.
*
* @providesModule resolveAssetSource
*/
'use strict';
var PixelRatio = require('PixelRatio');
var SourceCode = require('NativeModules').SourceCode;
var _serverURL;
function getServerURL() {
if (_serverURL === undefined) {
var scriptURL = SourceCode.scriptURL;
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
if (match) {
_serverURL = match[0];
} else {
_serverURL = null;
}
}
return _serverURL;
}
function pickScale(scales, deviceScale) {
// Packager guarantees that `scales` array is sorted
for (var i = 0; i < scales.length; i++) {
if (scales[i] >= deviceScale) {
return scales[i];
}
}
// If nothing matches, device scale is larger than any available
// scales, so we return the biggest one. Unless the array is empty,
// in which case we default to 1
return scales[scales.length - 1] || 1;
}
// TODO(frantic):
// * Pick best scale and append @Nx to file path
// * We are currently using httpServerLocation for both http and in-app bundle
function resolveAssetSource(source) {
if (!source.__packager_asset) {
return source;
}
// Deprecated assets are managed by Xcode for now,
// just returning image name as `uri`
// Examples:
// require('image!deprecatd_logo_example')
// require('./new-hotness-logo-example.png')
if (source.deprecated) {
return {
width: source.width,
height: source.height,
isStatic: true,
uri: source.name || source.uri, // TODO(frantic): remove uri
};
}
// TODO(frantic): currently httpServerLocation is used both as
// path in http URL and path within IPA. Should we have zipArchiveLocation?
var path = source.httpServerLocation;
if (path[0] === '/') {
path = path.substr(1);
}
var scale = pickScale(source.scales, PixelRatio.get());
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
var fileName = source.name + scaleSuffix + '.' + source.type;
var serverURL = getServerURL();
if (serverURL) {
return {
width: source.width,
height: source.height,
uri: serverURL + path + '/' + fileName +
'?hash=' + source.hash,
isStatic: false,
};
} else {
return {
width: source.width,
height: source.height,
uri: path + '/' + fileName,
isStatic: true,
};
}
return source;
}
module.exports = resolveAssetSource;
module.exports.pickScale = pickScale;

View File

@ -28,7 +28,7 @@ type Exception = {
function handleException(e: Exception) {
var stack = parseErrorStack(e);
console.error(
'Error: ' +
'Err0r: ' +
'\n stack: \n' + stackToString(stack) +
'\n URL: ' + e.sourceURL +
'\n line: ' + e.line +

View File

@ -7,8 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule loadSourceMap
*
* -- disabled flow due to mysterious validation errors --
* @flow
*/
'use strict';
@ -40,7 +39,10 @@ function fetchSourceMap(): Promise {
.then(response => response.text())
}
function extractSourceMapURL({url, text}): string {
function extractSourceMapURL({url, text, fullSourceMappingURL}): string {
if (fullSourceMappingURL) {
return fullSourceMappingURL;
}
var mapURL = SourceMapURL.getFrom(text);
var baseURL = url.match(/(.+:\/\/.*?)\//)[1];
return baseURL + mapURL;

View File

@ -9,7 +9,7 @@
#import <UIKit/UIKit.h>
#import "../../React/Base/RCTBridgeModule.h"
#import "RCTBridgeModule.h"
@interface RCTLinkingManager : NSObject <RCTBridgeModule>

View File

@ -36,6 +36,11 @@ RCT_EXPORT_MODULE()
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (dispatch_queue_t)methodQueue
{
return dispatch_queue_create("com.facebook.React.LinkingManager", DISPATCH_QUEUE_SERIAL);
}
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
@ -56,12 +61,14 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(openURL:(NSURL *)URL)
{
// Doesn't really matter what thread we call this on since it exits the app
[[UIApplication sharedApplication] openURL:URL];
}
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
callback:(RCTResponseSenderBlock)callback)
{
// This can be expensive, so we deliberately don't call on main thread
BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL];
callback(@[@(canOpen)]);
}

View File

@ -34,7 +34,7 @@ type ReachabilityStateIOS = $Enum<{
*
* ### reachabilityIOS
*
* Asyncronously determine if the device is online and on a cellular network.
* Asynchronously determine if the device is online and on a cellular network.
*
* - `none` - device is offline
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
@ -60,7 +60,7 @@ type ReachabilityStateIOS = $Enum<{
*
* ### isConnected
*
* Available on all platforms. Asyncronously fetch a boolean to determine
* Available on all platforms. Asynchronously fetch a boolean to determine
* internet connectivity.
*
* ```

View File

@ -204,6 +204,7 @@
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -212,6 +213,7 @@
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTNetwork;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -219,6 +221,7 @@
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -25,6 +25,8 @@ static NSString *const RCTReachabilityStateCell = @"cell";
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
{
RCTReachability *self = (__bridge id)info;
@ -53,8 +55,6 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC
}
}
RCT_EXPORT_MODULE()
#pragma mark - Lifecycle
- (instancetype)initWithHost:(NSString *)host
@ -71,7 +71,7 @@ RCT_EXPORT_MODULE()
- (instancetype)init
{
return [self initWithHost:@"http://apple.com"];
return [self initWithHost:@"apple.com"];
}
- (void)dealloc

View File

@ -53,13 +53,23 @@ class XMLHttpRequestBase {
}
getAllResponseHeaders(): ?string {
/* Stub */
return '';
if (this.responseHeaders) {
var headers = [];
for (var headerName in this.responseHeaders) {
headers.push(headerName + ': ' + this.responseHeaders[headerName]);
}
return headers.join('\n');
}
// according to the spec, return null <==> no response has been received
return null;
}
getResponseHeader(header: string): ?string {
/* Stub */
return '';
if (this.responseHeaders) {
var value = this.responseHeaders[header];
return value !== undefined ? value : null;
}
return null;
}
setRequestHeader(header: string, value: any): void {
@ -122,7 +132,7 @@ class XMLHttpRequestBase {
return;
}
this.status = status;
this.responseHeaders = responseHeaders;
this.responseHeaders = responseHeaders || {};
this.responseText = responseText;
this._setReadyState(this.DONE);
this._sendLoad();

View File

@ -198,6 +198,7 @@
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -206,6 +207,7 @@
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTPushNotification;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -213,6 +215,7 @@
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -9,7 +9,7 @@
#import <UIKit/UIKit.h>
#import "../../React/Base/RCTBridgeModule.h"
#import "RCTBridgeModule.h"
@interface RCTPushNotificationManager : NSObject <RCTBridgeModule>

View File

@ -113,7 +113,7 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
#endif
NSUInteger types;
NSUInteger types = 0;
if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) {
types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
} else {

View File

@ -246,6 +246,7 @@
580C37841AB104AF0015E709 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -257,6 +258,7 @@
XCTest,
);
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -264,6 +266,7 @@
580C37851AB104AF0015E709 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -10,19 +10,30 @@
#import <UIKit/UIKit.h>
#import "RCTBridgeModule.h"
#import "RCTDefines.h"
@class FBSnapshotTestController;
@interface RCTTestModule : NSObject <RCTBridgeModule>
// This is typically polled while running the runloop until true
@property (nonatomic, readonly, getter=isDone) BOOL done;
// This is used to give meaningful names to snapshot image files.
@property (nonatomic, assign) SEL testSelector;
/**
* The snapshot test controller for this module.
*/
@property (nonatomic, weak) FBSnapshotTestController *controller;
/**
* This is the view to be snapshotted.
*/
@property (nonatomic, weak) UIView *view;
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;
/**
* This is used to give meaningful names to snapshot image files.
*/
@property (nonatomic, assign) SEL testSelector;
/**
* This is typically polled while running the runloop until true.
*/
@property (nonatomic, readonly, getter=isDone) BOOL done;
@end

View File

@ -12,21 +12,25 @@
#import "FBSnapshotTestController.h"
#import "RCTAssert.h"
#import "RCTLog.h"
#import "RCTUIManager.h"
@implementation RCTTestModule
{
__weak FBSnapshotTestController *_snapshotController;
__weak UIView *_view;
NSMutableDictionary *_snapshotCounter;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view
- (dispatch_queue_t)methodQueue
{
return _bridge.uiManager.methodQueue;
}
- (instancetype)init
{
if ((self = [super init])) {
_snapshotController = controller;
_view = view;
_snapshotCounter = [NSMutableDictionary new];
}
return self;
@ -34,30 +38,29 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
{
if (!_snapshotController) {
RCTLogWarn(@"No snapshot controller configured.");
callback(@[]);
return;
}
RCTAssert(_controller != nil, @"No snapshot controller configured.");
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *testName = NSStringFromSelector(_testSelector);
_snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1);
_snapshotCounter[testName] = [@([_snapshotCounter[testName] integerValue] + 1) stringValue];
NSError *error = nil;
BOOL success = [_snapshotController compareSnapshotOfView:_view
selector:_testSelector
identifier:[_snapshotCounter[testName] stringValue]
error:&error];
BOOL success = [_controller compareSnapshotOfView:_view
selector:_testSelector
identifier:_snapshotCounter[testName]
error:&error];
RCTAssert(success, @"Snapshot comparison failed: %@", error);
callback(@[]);
});
}];
}
RCT_EXPORT_METHOD(markTestCompleted)
{
dispatch_async(dispatch_get_main_queue(), ^{
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
_done = YES;
});
}];
}
@end

View File

@ -10,6 +10,7 @@
#import "RCTTestRunner.h"
#import "FBSnapshotTestController.h"
#import "RCTDefines.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTTestModule.h"
@ -19,7 +20,7 @@
@implementation RCTTestRunner
{
FBSnapshotTestController *_snapshotController;
FBSnapshotTestController *_testController;
}
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir
@ -27,8 +28,8 @@
if ((self = [super init])) {
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
_snapshotController.referenceImagesDirectory = referenceDir;
_testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
_testController.referenceImagesDirectory = referenceDir;
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
}
return self;
@ -36,12 +37,12 @@
- (void)setRecordMode:(BOOL)recordMode
{
_snapshotController.recordMode = recordMode;
_testController.recordMode = recordMode;
}
- (BOOL)recordMode
{
return _snapshotController.recordMode;
return _testController.recordMode;
}
- (void)runTest:(SEL)test module:(NSString *)moduleName
@ -59,27 +60,24 @@
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
{
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
if ([vc.view isKindOfClass:[RCTRootView class]]) {
[(RCTRootView *)vc.view invalidate]; // Make sure the normal app view doesn't interfere
}
vc.view = [[UIView alloc] init];
RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil];
testModule.testSelector = test;
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:^(){
return @[testModule];
}
launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName];
testModule.view = rootView;
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:_scriptURL
moduleName:moduleName
launchOptions:nil];
rootView.initialProperties = initialProps;
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
testModule.controller = _testController;
testModule.testSelector = test;
testModule.view = rootView;
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
vc.view = [[UIView alloc] init];
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
#if RCT_DEBUG // Prevents build errors, as RCTRedBox is underfined if RCT_DEBUG=0
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
@ -100,6 +98,13 @@
} else {
RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
}
#else
expectErrorBlock(@"RCTRedBox unavailable. Set RCT_DEBUG=1 for testing.");
#endif
}
@end

View File

@ -8,7 +8,7 @@
/* Begin PBXBuildFile section */
00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */; };
00D277191AB8C35800DC1E48 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277181AB8C35800DC1E48 /* SRWebSocket.m */; };
13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20411AE707C5005F5298 /* SRWebSocket.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -26,8 +26,8 @@
/* Begin PBXFileReference section */
00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketExecutor.h; sourceTree = "<group>"; };
00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketExecutor.m; sourceTree = "<group>"; };
00D277171AB8C35800DC1E48 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = "<group>"; };
00D277181AB8C35800DC1E48 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = "<group>"; };
13AF20401AE707C5005F5298 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = "<group>"; };
13AF20411AE707C5005F5298 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = "<group>"; };
832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocketDebugger.a; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@ -45,8 +45,8 @@
832C81771AAF6DEF007FA2F7 = {
isa = PBXGroup;
children = (
00D277171AB8C35800DC1E48 /* SRWebSocket.h */,
00D277181AB8C35800DC1E48 /* SRWebSocket.m */,
13AF20401AE707C5005F5298 /* SRWebSocket.h */,
13AF20411AE707C5005F5298 /* SRWebSocket.m */,
00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */,
00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */,
832C81811AAF6DEF007FA2F7 /* Products */,
@ -119,7 +119,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00D277191AB8C35800DC1E48 /* SRWebSocket.m in Sources */,
13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */,
00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -214,6 +214,7 @@
832C81951AAF6DF0007FA2F7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -224,6 +225,7 @@
"-llibicucore",
);
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -231,6 +233,7 @@
832C81961AAF6DF0007FA2F7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -7,6 +7,10 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDefines.h"
#if RCT_DEV // Debug executors are only supported in dev mode
#import "RCTJavaScriptExecutor.h"
@interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor>
@ -14,3 +18,5 @@
- (instancetype)initWithURL:(NSURL *)URL;
@end
#endif

View File

@ -7,6 +7,10 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDefines.h"
#if RCT_DEV // Debug executors are only supported in dev mode
#import "RCTWebSocketExecutor.h"
#import "RCTLog.h"
@ -82,7 +86,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
{
__block NSError *initError;
dispatch_semaphore_t s = dispatch_semaphore_create(0);
[self sendMessage:@{@"method": @"prepareJSRuntime"} waitForReply:^(NSError *error, NSDictionary *reply) {
[self sendMessage:@{@"method": @"prepareJSRuntime"} context:nil waitForReply:^(NSError *error, NSDictionary *reply) {
initError = error;
dispatch_semaphore_signal(s);
}];
@ -111,7 +115,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
RCTLogError(@"WebSocket connection failed with error %@", error);
}
- (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback
- (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitForReply:(WSMessageCallback)callback
{
static NSUInteger lastID = 10000;
@ -122,6 +126,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
}];
callback(error, nil);
return;
} else if (executorID && ![RCTGetExecutorID(self) isEqualToNumber:executorID]) {
return;
}
NSNumber *expectedID = @(lastID++);
@ -134,22 +140,22 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [URL absoluteString], @"inject": _injectedObjects};
[self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) {
NSDictionary *message = @{@"method": @"executeApplicationScript", @"url": [URL absoluteString], @"inject": _injectedObjects};
[self sendMessage:message context:nil waitForReply:^(NSError *error, NSDictionary *reply) {
onComplete(error);
}];
}
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
NSDictionary *message = @{
@"method": NSStringFromSelector(_cmd),
@"method": @"executeJSCall",
@"moduleName": name,
@"moduleMethod": method,
@"arguments": arguments
};
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {
[self sendMessage:message context:executorID waitForReply:^(NSError *socketError, NSDictionary *reply) {
if (socketError) {
onComplete(nil, socketError);
return;
@ -169,6 +175,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
});
}
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
dispatch_async(dispatch_get_main_queue(), block);
}
- (void)invalidate
{
_socket.delegate = nil;
@ -187,3 +198,5 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
}
@end
#endif

View File

@ -19,6 +19,8 @@
#import <Availability.h>
#pragma clang diagnostic ignored "-Wshadow"
//NOTE: libicucore ins't actually needed for the socket to function
//and by commenting this out, we avoid the need to import it into every app.
@ -1702,7 +1704,7 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
for (int i = 0; i < maxCodepointSize; i++) {
NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO];
if (str) {
return data.length - i;
return (int32_t)(data.length - i);
}
}

View File

@ -19,6 +19,7 @@ var TextInputState = require('TextInputState');
var flattenStyle = require('flattenStyle');
var invariant = require('invariant');
var mergeFast = require('mergeFast');
var precomputeStyle = require('precomputeStyle');
type MeasureOnSuccessCallback = (
x: number,
@ -93,7 +94,7 @@ var NativeMethodsMixin = {
break;
}
}
var style = flattenStyle(nativeProps.style);
var style = precomputeStyle(flattenStyle(nativeProps.style));
var props = null;
if (hasOnlyStyle) {

View File

@ -23,6 +23,7 @@ var styleDiffer = require('styleDiffer');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
var diffRawProperties = require('diffRawProperties');
var flattenStyle = require('flattenStyle');
var precomputeStyle = require('precomputeStyle');
var warning = require('warning');
var registrationNames = ReactIOSEventEmitter.registrationNames;
@ -160,7 +161,7 @@ ReactIOSNativeComponent.Mixin = {
// before actually doing the expensive flattening operation in order to
// compute the diff.
if (styleDiffer(nextProps.style, prevProps.style)) {
var nextFlattenedStyle = flattenStyle(nextProps.style);
var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style));
updatePayload = diffRawProperties(
updatePayload,
this.previousFlattenedStyle,

View File

@ -41,18 +41,19 @@ function requireNativeComponent(
viewName: string,
wrapperComponent: ?Function
): Function {
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName];
if (!viewConfig) {
var viewConfig = RCTUIManager[viewName];
if (!viewConfig || !viewConfig.nativeProps) {
return UnimplementedView;
}
var nativeProps = {
...RCTUIManager.viewConfigs.RCTView.nativeProps,
...RCTUIManager.RCTView.nativeProps,
...viewConfig.nativeProps,
};
viewConfig.uiViewClassName = viewName;
viewConfig.validAttributes = {};
for (var key in nativeProps) {
// TODO: deep diff by default in diffRawProperties instead of setting it here
var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer;
var differ = TypeToDifferMap[nativeProps[key]] || deepDiffer;
viewConfig.validAttributes[key] = {diff: differ};
}
if (__DEV__) {

View File

@ -23,7 +23,7 @@ function verifyPropTypes(
return; // This happens for UnimplementedView.
}
var nativeProps = viewConfig.nativeProps;
for (var prop in viewConfig.nativeProps) {
for (var prop in nativeProps) {
if (!component.propTypes[prop] &&
!View.propTypes[prop] &&
!ReactIOSStyleAttributes[prop] &&

View File

@ -0,0 +1,161 @@
/**
* 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.
*
* @providesModule precomputeStyle
* @flow
*/
'use strict';
var MatrixMath = require('MatrixMath');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
var invariant = require('invariant');
/**
* This method provides a hook where flattened styles may be precomputed or
* otherwise prepared to become better input data for native code.
*/
function precomputeStyle(style: ?Object): ?Object {
if (!style || !style.transform) {
return style;
}
invariant(
!style.transformMatrix,
'transformMatrix and transform styles cannot be used on the same component'
);
var newStyle = _precomputeTransforms({...style});
deepFreezeAndThrowOnMutationInDev(newStyle);
return newStyle;
}
/**
* Generate a transform matrix based on the provided transforms, and use that
* within the style object instead.
*
* This allows us to provide an API that is similar to CSS and to have a
* universal, singular interface to native code.
*/
function _precomputeTransforms(style: Object): Object {
var {transform} = style;
var result = MatrixMath.createIdentityMatrix();
transform.forEach(transformation => {
var key = Object.keys(transformation)[0];
var value = transformation[key];
if (__DEV__) {
_validateTransform(key, value, transformation);
}
switch (key) {
case 'matrix':
MatrixMath.multiplyInto(result, result, value);
break;
case 'rotate':
_multiplyTransform(result, MatrixMath.reuseRotateZCommand, [_convertToRadians(value)]);
break;
case 'scale':
_multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]);
break;
case 'scaleX':
_multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]);
break;
case 'scaleY':
_multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]);
break;
case 'translate':
_multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [value[0], value[1], value[2] || 0]);
break;
case 'translateX':
_multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [value, 0]);
break;
case 'translateY':
_multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [0, value]);
break;
default:
throw new Error('Invalid transform name: ' + key);
}
});
return {
...style,
transformMatrix: result,
};
}
/**
* Performs a destructive operation on a transform matrix.
*/
function _multiplyTransform(
result: Array<number>,
matrixMathFunction: Function,
args: Array<number>
): void {
var matrixToApply = MatrixMath.createIdentityMatrix();
var argsWithIdentity = [matrixToApply].concat(args);
matrixMathFunction.apply(this, argsWithIdentity);
MatrixMath.multiplyInto(result, result, matrixToApply);
}
/**
* Parses a string like '0.5rad' or '60deg' into radians expressed in a float.
* Note that validation on the string is done in `_validateTransform()`.
*/
function _convertToRadians(value: string): number {
var floatValue = parseFloat(value, 10);
return value.indexOf('rad') > -1 ? floatValue : floatValue * Math.PI / 180;
}
function _validateTransform(key, value, transformation) {
var multivalueTransforms = [
'matrix',
'translate',
];
if (multivalueTransforms.indexOf(key) !== -1) {
invariant(
Array.isArray(value),
'Transform with key of %s must have an array as the value: %s',
key,
JSON.stringify(transformation)
);
}
switch (key) {
case 'matrix':
invariant(
value.length === 9 || value.length === 16,
'Matrix transform must have a length of 9 (2d) or 16 (3d). ' +
'Provided matrix has a length of %s: %s',
value.length,
JSON.stringify(transformation)
);
break;
case 'translate':
break;
case 'rotate':
invariant(
typeof value === 'string',
'Transform with key of "%s" must be a string: %s',
key,
JSON.stringify(transformation)
);
invariant(
value.indexOf('deg') > -1 || value.indexOf('rad') > -1,
'Rotate transform must be expressed in degrees (deg) or radians ' +
'(rad): %s',
JSON.stringify(transformation)
);
break;
default:
invariant(
typeof value === 'number',
'Transform with key of "%s" must be a number: %s',
key,
JSON.stringify(transformation)
);
}
}
module.exports = precomputeStyle;

View File

@ -22,15 +22,21 @@ static css_dim_t RCTMeasure(void *context, float width)
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
NSTextStorage *previousTextStorage = shadowText.layoutManager.textStorage;
if (previousTextStorage) {
[previousTextStorage removeLayoutManager:shadowText.layoutManager];
}
[textStorage addLayoutManager:shadowText.layoutManager];
shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
shadowText.layoutManager.textStorage = textStorage;
[shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
[textStorage removeLayoutManager:shadowText.layoutManager];
if (previousTextStorage) {
[previousTextStorage addLayoutManager:shadowText.layoutManager];
}
css_dim_t result;
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);

View File

@ -25,6 +25,7 @@
if ((self = [super initWithFrame:frame])) {
_textStorage = [[NSTextStorage alloc] init];
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
@ -94,21 +95,18 @@
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
}
- (void)layoutSubviews
{
[super layoutSubviews];
// The header comment for `size` says that a height of 0.0 should be enough,
// but it isn't.
_textContainer.size = CGSizeMake([self textFrame].size.width, CGFLOAT_MAX);
}
- (void)drawRect:(CGRect)rect
{
CGPoint origin = [self textFrame].origin;
CGRect textFrame = [self textFrame];
// We reset the text container size every time because RCTShadowText's
// RCTMeasure overrides it. The header comment for `size` says that a height
// of 0.0 should be enough, but it isn't.
_textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX);
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point

View File

@ -222,6 +222,7 @@
58B511B01A9E6C1300147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -229,6 +230,7 @@
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -236,6 +238,7 @@
58B511B11A9E6C1300147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -46,7 +46,7 @@ RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(maxNumberOfLines, NSInteger)
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)

View File

@ -37,7 +37,7 @@ var DEFAULT_BUTTON = {
* {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
* {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
* ]
* )}
* )
* ```
*/

131
Libraries/Utilities/MatrixMath.js Executable file
View File

@ -0,0 +1,131 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule MatrixMath
*/
'use strict';
/**
* Memory conservative (mutative) matrix math utilities. Uses "command"
* matrices, which are reusable.
*/
var MatrixMath = {
createIdentityMatrix: function() {
return [
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1
];
},
createCopy: function(m) {
return [
m[0], m[1], m[2], m[3],
m[4], m[5], m[6], m[7],
m[8], m[9], m[10], m[11],
m[12], m[13], m[14], m[15],
];
},
createTranslate2d: function(x, y) {
var mat = MatrixMath.createIdentityMatrix();
MatrixMath.reuseTranslate2dCommand(mat, x, y);
return mat;
},
reuseTranslate2dCommand: function(matrixCommand, x, y) {
matrixCommand[12] = x;
matrixCommand[13] = y;
},
reuseTranslate3dCommand: function(matrixCommand, x, y, z) {
matrixCommand[12] = x;
matrixCommand[13] = y;
matrixCommand[14] = z;
},
createScale: function(factor) {
var mat = MatrixMath.createIdentityMatrix();
MatrixMath.reuseScaleCommand(mat, factor);
return mat;
},
reuseScaleCommand: function(matrixCommand, factor) {
matrixCommand[0] = factor;
matrixCommand[5] = factor;
},
reuseScale3dCommand: function(matrixCommand, x, y, z) {
matrixCommand[0] = x;
matrixCommand[5] = y;
matrixCommand[10] = z;
},
reuseScaleXCommand(matrixCommand, factor) {
matrixCommand[0] = factor;
},
reuseScaleYCommand(matrixCommand, factor) {
matrixCommand[5] = factor;
},
reuseScaleZCommand(matrixCommand, factor) {
matrixCommand[10] = factor;
},
reuseRotateYCommand: function(matrixCommand, amount) {
matrixCommand[0] = Math.cos(amount);
matrixCommand[2] = Math.sin(amount);
matrixCommand[8] = Math.sin(-amount);
matrixCommand[10] = Math.cos(amount);
},
createRotateZ: function(radians) {
var mat = MatrixMath.createIdentityMatrix();
MatrixMath.reuseRotateZCommand(mat, radians);
return mat;
},
// http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix
reuseRotateZCommand: function(matrixCommand, radians) {
matrixCommand[0] = Math.cos(radians);
matrixCommand[1] = Math.sin(radians);
matrixCommand[4] = -Math.sin(radians);
matrixCommand[5] = Math.cos(radians);
},
multiplyInto: function(out, a, b) {
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
}
};
module.exports = MatrixMath;

View File

@ -10,7 +10,9 @@
* @flow
*/
'use strict';
var ErrorUtils = require('ErrorUtils');
var ReactUpdates = require('ReactUpdates');
var invariant = require('invariant');
var warning = require('warning');
@ -307,21 +309,23 @@ var MessageQueueMixin = {
);
},
processBatch: function (batch) {
processBatch: function(batch) {
var self = this;
batch.forEach(function (call) {
invariant(
call.module === 'BatchedBridge',
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self.callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self.invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
ReactUpdates.batchedUpdates(function() {
batch.forEach(function(call) {
invariant(
call.module === 'BatchedBridge',
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self.callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self.invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
});
});
return this.flushedQueue();
},

View File

@ -36,8 +36,8 @@ var Dimensions = require('Dimensions');
*
* ```
* var image = getImage({
* width: 200 * PixelRatio.get(),
* height: 100 * PixelRatio.get()
* width: PixelRatio.getPixelSizeForLayoutSize(200),
* height: PixelRatio.getPixelSizeForLayoutSize(100),
* });
* <Image source={image} style={{width: 200, height: 100}} />
* ```
@ -52,10 +52,21 @@ class PixelRatio {
* - iPhone 6
* - PixelRatio.get() === 3
* - iPhone 6 plus
* - PixelRatio.get() === 3.5
* - Nexus 6
*/
static get(): number {
return Dimensions.get('window').scale;
}
/**
* Converts a layout size (dp) to pixel size (px).
*
* Guaranteed to return an integer number.
*/
static getPixelSizeForLayoutSize(layoutSize: number): number {
return Math.round(layoutSize * PixelRatio.get());
}
}
// No-op for iOS, but used on the web. Should not be documented.

View File

@ -0,0 +1,102 @@
/**
* 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.
*/
'use strict';
jest.dontMock('deepDiffer');
var deepDiffer = require('deepDiffer');
describe('deepDiffer', function() {
it('should diff primitives of the same type', () => {
expect(deepDiffer(1, 2)).toBe(true);
expect(deepDiffer(42, 42)).toBe(false);
expect(deepDiffer('foo', 'bar')).toBe(true);
expect(deepDiffer('foo', 'foo')).toBe(false);
expect(deepDiffer(true, false)).toBe(true);
expect(deepDiffer(false, true)).toBe(true);
expect(deepDiffer(true, true)).toBe(false);
expect(deepDiffer(false, false)).toBe(false);
expect(deepDiffer(null, null)).toBe(false);
expect(deepDiffer(undefined, undefined)).toBe(false);
});
it('should diff primitives of different types', () => {
expect(deepDiffer(1, '1')).toBe(true);
expect(deepDiffer(true, 'true')).toBe(true);
expect(deepDiffer(true, 1)).toBe(true);
expect(deepDiffer(false, 0)).toBe(true);
expect(deepDiffer(null, undefined)).toBe(true);
expect(deepDiffer(null, 0)).toBe(true);
expect(deepDiffer(null, false)).toBe(true);
expect(deepDiffer(null, '')).toBe(true);
expect(deepDiffer(undefined, 0)).toBe(true);
expect(deepDiffer(undefined, false)).toBe(true);
expect(deepDiffer(undefined, '')).toBe(true);
});
it('should diff Objects', () => {
expect(deepDiffer({}, {})).toBe(false);
expect(deepDiffer({}, null)).toBe(true);
expect(deepDiffer(null, {})).toBe(true);
expect(deepDiffer({a: 1}, {a: 1})).toBe(false);
expect(deepDiffer({a: 1}, {a: 2})).toBe(true);
expect(deepDiffer({a: 1}, {a: 1, b: null})).toBe(true);
expect(deepDiffer({a: 1}, {a: 1, b: 1})).toBe(true);
expect(deepDiffer({a: 1, b: 1}, {a: 1})).toBe(true);
expect(deepDiffer({a: {A: 1}, b: 1}, {a: {A: 1}, b: 1})).toBe(false);
expect(deepDiffer({a: {A: 1}, b: 1}, {a: {A: 2}, b: 1})).toBe(true);
expect(deepDiffer(
{a: {A: {aA: 1, bB: 1}}, b: 1},
{a: {A: {aA: 1, bB: 1}}, b: 1}
)).toBe(false);
expect(deepDiffer(
{a: {A: {aA: 1, bB: 1}}, b: 1},
{a: {A: {aA: 1, cC: 1}}, b: 1}
)).toBe(true);
});
it('should diff Arrays', () => {
expect(deepDiffer([], [])).toBe(false);
expect(deepDiffer([], null)).toBe(true);
expect(deepDiffer(null, [])).toBe(true);
expect(deepDiffer([42], [42])).toBe(false);
expect(deepDiffer([1], [2])).toBe(true);
expect(deepDiffer([1, 2, 3], [1, 2, 3])).toBe(false);
expect(deepDiffer([1, 2, 3], [1, 2, 4])).toBe(true);
expect(deepDiffer([1, 2, 3], [1, 4, 3])).toBe(true);
expect(deepDiffer([1, 2, 3, 4], [1, 2, 3])).toBe(true);
expect(deepDiffer([1, 2, 3], [1, 2, 3, 4])).toBe(true);
expect(deepDiffer([0, null, false, ''], [0, null, false, ''])).toBe(false);
expect(deepDiffer([0, null, false, ''], ['', false, null, 0])).toBe(true);
});
it('should diff mixed types', () => {
expect(deepDiffer({}, [])).toBe(true);
expect(deepDiffer([], {})).toBe(true);
expect(deepDiffer(
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]}
)).toBe(false);
expect(deepDiffer(
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
{a: [{A: {aA: 1, bB: 2}}, 'bar'], c: [1, [false]]}
)).toBe(true);
expect(deepDiffer(
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false], null]}
)).toBe(true);
expect(deepDiffer(
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
{a: [{A: {aA: 1, bB: 1}}, ['bar']], c: [1, [false]]}
)).toBe(true);
});
it('should distinguish between proper Array and Object', () => {
expect(deepDiffer(['a', 'b'], {0: 'a', 1: 'b', length: 2})).toBe(true);
expect(deepDiffer(['a', 'b'], {length: 2, 0: 'a', 1: 'b'})).toBe(true);
});
it('should diff same object', () => {
var obj = [1,[2,3]];
expect(deepDiffer(obj, obj)).toBe(false);
});
});

View File

@ -35,16 +35,29 @@ var deepDiffer = function(one: any, two: any): bool {
if (one.constructor !== two.constructor) {
return true;
}
for (var key in one) {
if (deepDiffer(one[key], two[key])) {
if (Array.isArray(one)) {
// We know two is also an array because the constructors are equal
var len = one.length;
if (two.length !== len) {
return true;
}
}
for (var twoKey in two) {
// The only case we haven't checked yet is keys that are in two but aren't
// in one, which means they are different.
if (one[twoKey] === undefined && two[twoKey] !== undefined) {
return true;
for (var ii = 0; ii < len; ii++) {
if (deepDiffer(one[ii], two[ii])) {
return true;
}
}
} else {
for (var key in one) {
if (deepDiffer(one[key], two[key])) {
return true;
}
}
for (var twoKey in two) {
// The only case we haven't checked yet is keys that are in two but aren't
// in one, which means they are different.
if (one[twoKey] === undefined && two[twoKey] !== undefined) {
return true;
}
}
}
return false;

View File

@ -208,6 +208,7 @@
832C81951AAF6DF0007FA2F7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
@ -215,6 +216,7 @@
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
@ -222,6 +224,7 @@
832C81961AAF6DF0007FA2F7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -9,21 +9,7 @@
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* By default, only raise an NSAssertion in debug mode
* (custom assert functions will still be called).
*/
#ifndef RCT_ASSERT
#if DEBUG
#define RCT_ASSERT 1
#else
#define RCT_ASSERT 0
#endif
#endif
#import "RCTDefines.h"
/**
* The default error domain to be used for React errors.
@ -44,13 +30,14 @@ typedef void (^RCTAssertFunction)(
/**
* Private logging function - ignore this.
*/
void _RCTAssertFormat(BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6);
RCT_EXTERN void _RCTAssertFormat(
BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6);
/**
* This is the main assert macro that you should use.
*/
#define RCTAssert(condition, ...) do { BOOL pass = ((condition) != 0); \
if (RCT_ASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
if (RCT_NSASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; } \
_RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \
} while (false)
@ -66,16 +53,12 @@ _RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \
* macros. You can use these to replace the standard behavior with custom log
* functionality.
*/
void RCTSetAssertFunction(RCTAssertFunction assertFunction);
RCTAssertFunction RCTGetAssertFunction(void);
RCT_EXTERN void RCTSetAssertFunction(RCTAssertFunction assertFunction);
RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void);
/**
* This appends additional code to the existing assert function, without
* replacing the existing functionality. Useful if you just want to forward
* assert info to an extra service without changing the default behavior.
*/
void RCTAddAssertFunction(RCTAssertFunction assertFunction);
#ifdef __cplusplus
}
#endif
RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction);

View File

@ -10,6 +10,7 @@
#import <UIKit/UIKit.h>
#import "RCTBridgeModule.h"
#import "RCTDefines.h"
#import "RCTFrameUpdate.h"
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"
@ -40,7 +41,7 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
/**
* This function returns the module name for a given class.
*/
extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
/**
* Async batched bridge used to communicate with the JavaScript application.
@ -62,9 +63,9 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
/**
* 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. Method should be regsitered using the
* with the JavaScript code. Method should be registered using the
* RCT_IMPORT_METHOD macro below. Attempting to call a method that has not been
* registered will result in an error.
* registered will result in an error. Safe to call from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
@ -88,6 +89,11 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
/**
* URL of the script that was loaded into the bridge.
*/
@property (nonatomic, copy) NSURL *bundleURL;
@property (nonatomic, strong) Class executorClass;
/**
@ -101,13 +107,6 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
*/
@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;
/**
* The launch options that were used to initialize the bridge.
*/
@ -119,17 +118,17 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
/**
* Reload the bundle and reset executor and modules.
* Reload the bundle and reset executor & modules. Safe to call from any thread.
*/
- (void)reload;
/**
* Add a new observer that will be called on every screen refresh
* Add a new observer that will be called on every screen refresh.
*/
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
/**
* Stop receiving screen refresh updates for the given observer
* Stop receiving screen refresh updates for the given observer.
*/
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;

View File

@ -22,6 +22,7 @@
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTSparseArray.h"
@ -48,39 +49,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
*/
#define BATCHED_BRIDGE 1
#ifdef DEBUG
#define RCT_PROFILE_START() \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
NSTimeInterval __rct_profile_start = CACurrentMediaTime() \
_Pragma("clang diagnostic pop")
#define RCT_PROFILE_END(cat, args, profileName...) \
do { \
if (_profile) { \
[_profileLock lock]; \
[_profile addObject:@{ \
@"name": [@[profileName] componentsJoinedByString: @"_"], \
@"cat": @ #cat, \
@"ts": @((NSUInteger)((__rct_profile_start - _startingTime) * 1e6)), \
@"dur": @((NSUInteger)((CACurrentMediaTime() - __rct_profile_start) * 1e6)), \
@"ph": @"X", \
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
@"tid": [[NSThread currentThread] description], \
@"args": args ?: [NSNull null], \
}]; \
[_profileLock unlock]; \
} \
} while(0)
#else
#define RCT_PROFILE_START(...)
#define RCT_PROFILE_END(...)
#endif
#ifdef __LP64__
typedef uint64_t RCTHeaderValue;
typedef struct section_64 RCTHeaderSection;
@ -175,7 +143,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
// Get data entry
NSString *entry = @(*(const char **)(mach_header + addr));
NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}] componentsSeparatedByString:@" "];
NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}]
componentsSeparatedByString:@" "];
// Parse class name
NSString *moduleClassName = parts[0];
@ -196,33 +165,32 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
}
}
#if DEBUG
if (RCT_DEBUG) {
// We may be able to get rid of this check in future, once people
// get used to the new registration system. That would potentially
// allow you to create modules that are not automatically registered
// We may be able to get rid of this check in future, once people
// get used to the new registration system. That would potentially
// allow you to create modules that are not automatically registered
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++)
{
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (![RCTModuleClassesByID containsObject:cls]) {
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClassesByID containsObject:cls]) {
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
}
break;
}
break;
superclass = class_getSuperclass(superclass);
}
superclass = class_getSuperclass(superclass);
}
}
#endif
});
return RCTModuleClassesByID;
@ -230,16 +198,15 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@interface RCTBridge ()
@property (nonatomic, copy, readonly) NSArray *profile;
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
arguments:(NSArray *)args
context:(NSNumber *)context;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
arguments:(NSArray *)args
context:(NSNumber *)context;
@end
/**
@ -249,6 +216,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@property (nonatomic, copy, readonly) NSString *moduleClassName;
@property (nonatomic, copy, readonly) NSString *JSMethodName;
@property (nonatomic, assign, readonly) SEL selector;
@end
@ -260,6 +228,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks;
NSString *_methodName;
dispatch_block_t _methodQueue;
}
static Class _globalExecutorClass;
@ -320,13 +289,13 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
_isClassMethod = [reactMethodName characterAtIndex:0] == '+';
_moduleClass = NSClassFromString(_moduleClassName);
#if DEBUG
if (RCT_DEBUG) {
// Sanity check
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"You are attempting to export the method %@, but %@ does not \
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
#endif
// Sanity check
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"You are attempting to export the method %@, but %@ does not \
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
}
// Get method signature
_methodSignature = _isClassMethod ?
@ -338,14 +307,15 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
}]; \
void (^addBlockArgument)(void) = ^{
RCT_ARG_BLOCK(
if (json && ![json isKindOfClass:[NSNumber class]]) {
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
@ -355,7 +325,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
__autoreleasing id value = (json ? ^(NSArray *args) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, args]];
arguments:@[json, args]
context:context];
} : ^(NSArray *unused) {});
)
};
@ -404,10 +375,14 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
case '{':
RCTAssert(NO, @"Argument %zd of %C[%@ %@] is defined as %@, however RCT_EXPORT_METHOD() "
"does not currently support struct-type arguments.", i - 2,
[reactMethodName characterAtIndex:0], _moduleClassName,
objCMethodName, argumentName);
break;
default:
defaultCase(argumentType);
break;
}
} else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
addBlockArgument();
@ -421,7 +396,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
#define RCT_CASE(_value, _class, _logic) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \
if (RCT_DEBUG && json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
@ -437,7 +412,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \
if (RCT_DEBUG && json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
@ -463,7 +438,6 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
default:
defaultCase(argumentType);
break;
}
}
}
@ -477,21 +451,21 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
context:(NSNumber *)context
{
if (RCT_DEBUG) {
#if DEBUG
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", _methodName, [module class]);
// 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",
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
arguments.count, _argumentBlocks.count);
return;
// Safety check
if (arguments.count != _argumentBlocks.count) {
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
arguments.count, _argumentBlocks.count);
return;
}
}
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
@ -503,8 +477,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
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);
void (^block)(RCTBridge *, NSNumber *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
block(bridge, context, invocation, index + 2, arg);
index++;
}
@ -653,7 +627,6 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
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.
@ -724,7 +697,7 @@ static NSDictionary *RCTLocalModulesConfig()
@interface RCTDisplayLink : NSObject <RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector NS_DESIGNATED_INITIALIZER;
@end
@ -738,14 +711,16 @@ static NSDictionary *RCTLocalModulesConfig()
{
__weak RCTBridge *_bridge;
CADisplayLink *_displayLink;
SEL _selector;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector
{
if ((self = [super init])) {
_bridge = bridge;
_selector = selector;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
return self;
}
@ -765,7 +740,10 @@ static NSDictionary *RCTLocalModulesConfig()
- (void)_update:(CADisplayLink *)displayLink
{
[_bridge _update:displayLink];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_bridge performSelector:_selector withObject:displayLink];
#pragma clang diagnostic pop
}
@end
@ -792,20 +770,19 @@ static NSDictionary *RCTLocalModulesConfig()
@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSURL *_bundleURL;
RCTBridgeModuleProviderBlock _moduleProvider;
RCTDisplayLink *_displayLink;
RCTDisplayLink *_vsyncDisplayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
NSMutableArray *_scheduledCallbacks;
RCTSparseArray *_scheduledCallbacks;
BOOL _loading;
NSUInteger _startingTime;
NSMutableArray *_profile;
NSLock *_profileLock;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
@ -821,20 +798,24 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[self setUp];
[self bindKeys];
}
return self;
}
- (void)setUp
{
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
_javaScriptExecutor = [[executorClass alloc] init];
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)];
}];
_vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)];
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
@ -848,21 +829,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[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])) {
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,
@"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
id<RCTBridgeModule> module = [[moduleClass alloc] init];
if (module) {
_modulesByID[moduleID] = modulesByName[moduleName] = module;
}
module = [[moduleClass alloc] init];
}
if (module) {
// Store module instance
_modulesByID[moduleID] = modulesByName[moduleName] = module;
}
}];
@ -876,6 +865,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
// Get method queues
_queuesByID = [[RCTSparseArray alloc] init];
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t queue = [module methodQueue];
if (queue) {
_queuesByID[moduleID] = queue;
}
}
}];
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
@ -902,6 +902,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) {
_loading = NO;
if (error != nil) {
#if RCT_DEBUG // Red box is only available in debug mode
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
@ -910,14 +913,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withDetails:[error localizedFailureReason]];
}
#endif
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
@ -955,6 +957,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
strongSelf.executorClass = Nil;
[strongSelf reload];
}];
#if RCT_DEV // Debug executors are only available in dev mode
// reload in debug mode
[commands registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
@ -971,10 +976,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[strongSelf reload];
}];
#endif
#endif
}
- (NSDictionary *)modules
{
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
@ -1016,6 +1021,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_javaScriptExecutor = nil;
[_displayLink invalidate];
[_vsyncDisplayLink invalidate];
_frameUpdateObservers = nil;
// Invalidate modules
@ -1027,6 +1033,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_queuesByID = nil;
_modulesByName = nil;
}
@ -1054,7 +1061,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
if (!_loading) {
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]];
arguments:@[moduleID, methodID, args ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
}
@ -1075,13 +1083,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
#if BATCHED_BRIDGE
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]];
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
#else
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]];
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
#endif
}
}
@ -1089,23 +1099,28 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCT_PROFILE_START();
RCTProfileBeginEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script");
RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError);
if (scriptLoadError) {
onComplete(scriptLoadError);
return;
}
RCT_PROFILE_START();
RCTProfileBeginEvent();
NSNumber *context = RCTGetExecutorID(_javaScriptExecutor);
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
method:@"flushedQueue"
arguments:@[]
context:context
callback:^(id json, NSError *error) {
RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue");
RCT_PROFILE_START();
[self _handleBuffer:json];
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{
@"json": json ?: [NSNull null],
@"error": error ?: [NSNull null],
});
[self _handleBuffer:json context:context];
onComplete(error);
}];
}];
@ -1113,10 +1128,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
#pragma mark - Payload Generation
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#if BATCHED_BRIDGE
RCT_PROFILE_START();
RCTProfileBeginEvent();
if ([module isEqualToString:@"RCTEventEmitter"]) {
for (NSDictionary *call in _scheduledCalls) {
@ -1130,55 +1145,55 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
@"module": module,
@"method": method,
@"args": args,
@"context": context ?: @0,
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
[_scheduledCallbacks addObject:call];
_scheduledCallbacks[args[0]] = call;
} else {
[_scheduledCalls addObject:call];
}
RCT_PROFILE_END(js_call, args, @"schedule", module, method);
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
}
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#endif
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
NSString *moduleDotMethod = [NSString stringWithFormat:@"%@.%@", module, method];
RCT_PROFILE_START();
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
RCT_PROFILE_END(js_call, args, moduleDotMethod);
RCT_PROFILE_START();
[self _handleBuffer:json];
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
[self _handleBuffer:json context:context];
};
[_javaScriptExecutor executeJSCall:module
method:method
arguments:args
context:context
callback:processResponse];
}
#pragma mark - Payload Processing
- (void)_handleBuffer:(id)buffer
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
{
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;
}
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;
@ -1192,56 +1207,75 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
#endif
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) {
if (RCT_DEBUG && (numRequests != methodIDs.count || numRequests != paramsArrays.count)) {
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
return;
}
// TODO: if we sort the requests by module, we could dispatch once per
// module instead of per request, which would reduce the call overhead.
for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool {
[self _handleRequestNumber:i
moduleID:[moduleIDs[i] integerValue]
methodID:[methodIDs[i] integerValue]
params:paramsArrays[i]];
params:paramsArrays[i]
context:context];
}
}
// 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)]) {
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
if ([module respondsToSelector:@selector(batchDidComplete)]) {
dispatch_queue_t queue = _queuesByID[moduleID];
dispatch_async(queue ?: _methodQueue, ^{
[module batchDidComplete];
}
});
}
});
}];
}
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID
methodID:(NSUInteger)methodID
params:(NSArray *)params
context:(NSNumber *)context
{
if (![params isKindOfClass:[NSArray class]]) {
if (RCT_DEBUG && ![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) {
if (RCT_DEBUG && methodID >= methods.count) {
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]);
return NO;
}
RCTModuleMethod *method = methods[methodID];
// Look up module
id module = self->_modulesByID[moduleID];
if (RCT_DEBUG && !module) {
RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
return NO;
}
__weak RCTBridge *weakSelf = self;
dispatch_async(self.shadowQueue, ^{
dispatch_queue_t queue = _queuesByID[moduleID];
dispatch_async(queue ?: _methodQueue, ^{
RCTProfileBeginEvent();
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
@ -1250,30 +1284,34 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
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);
if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
@throw;
if (!RCT_DEBUG) {
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
} else {
@try {
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
@throw;
}
}
}
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
@"module": method.moduleClassName,
@"method": method.JSMethodName,
@"selector": NSStringFromSelector(method.selector),
});
});
return YES;
}
- (void)_update:(CADisplayLink *)displayLink
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCT_PROFILE_START();
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
RCTProfileBeginEvent();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
@ -1282,25 +1320,30 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
[self _runScheduledCalls];
RCT_PROFILE_END(display_link, nil, @"main_thread");
}
- (void)_runScheduledCalls
{
#if BATCHED_BRIDGE
NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls];
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
return [call[@"context"] isEqualToNumber:currentExecutorID];
}]];
if (calls.count > 0) {
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"processBatch"
arguments:@[calls]];
method:@"processBatch"
arguments:@[calls]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
#endif
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
{
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
}
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
@ -1315,13 +1358,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)reload
{
if (!_loading) {
// If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render.
[self invalidate];
[self setUp];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (!_loading) {
// If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render.
[self invalidate];
[self setUp];
}
});
}
+ (void)logMessage:(NSString *)message level:(NSString *)level
@ -1335,6 +1380,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:@[level, message]
context:RCTGetExecutorID(_latestJSExecutor)
callback:^(id json, NSError *error) {}];
}
@ -1344,23 +1390,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
RCTLogError(@"To run the profiler you must be running from the dev server");
return;
}
_profileLock = [[NSLock alloc] init];
_startingTime = CACurrentMediaTime();
[_profileLock lock];
_profile = [[NSMutableArray alloc] init];
[_profileLock unlock];
RCTProfileInit();
}
- (void)stopProfiling
{
[_profileLock lock];
NSArray *profile = _profile;
_profile = nil;
[_profileLock unlock];
_profileLock = nil;
NSString *log = RCTJSONStringify(profile, NULL);
NSString *log = RCTProfileEnd();
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];

View File

@ -73,12 +73,67 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
* { ... }
*/
#define RCT_REMAP_METHOD(js_name, method) \
RCT_EXTERN_REMAP_METHOD(js_name, method) \
- (void)method
/**
* Use this macro in a private Objective-C implementation file to automatically
* register an external module with the bridge when it loads. This allows you to
* register Swift or private Objective-C classes with the bridge.
*
* For example if one wanted to export a Swift class to the bridge:
*
* MyModule.swift:
*
* @objc(MyModule) class MyModule: NSObject {
*
* @objc func doSomething(string: String! withFoo a: Int, bar b: Int) { ... }
*
* }
*
* MyModuleExport.m:
*
* #import "RCTBridgeModule.h"
*
* @interface RCT_EXTERN_MODULE(MyModule, NSObject)
*
* RCT_EXTERN_METHOD(doSomething:(NSString *)string withFoo:(NSInteger)a bar:(NSInteger)b)
*
* @end
*
* This will now expose MyModule and the method to JavaScript via
* `NativeModules.MyModule.doSomething`
*/
#define RCT_EXTERN_MODULE(objc_name, objc_supername) \
RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
/**
* Similar to RCT_EXTERN_MODULE but allows setting a custom JavaScript name
*/
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
objc_name : objc_supername \
@end \
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE(js_name)
/**
* Use this macro in accordance with RCT_EXTERN_MODULE to export methods
* of an external module.
*/
#define RCT_EXTERN_METHOD(method) \
RCT_EXTERN_REMAP_METHOD(, method)
/**
* Similar to RCT_EXTERN_REMAP_METHOD but allows setting a custom JavaScript name
*/
#define RCT_EXTERN_REMAP_METHOD(js_name, method) \
- (void)__rct_export__##method { \
__attribute__((used, section("__DATA,RCTExport"))) \
__attribute__((__aligned__(1))) \
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
} \
- (void)method
}
/**
* Deprecated, do not use.
@ -89,6 +144,37 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
__attribute__((__aligned__(1))) \
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
/**
* The queue that will be used to call all exported methods. If omitted, this
* will call on the default background queue, which is avoids blocking the main
* thread.
*
* If the methods in your module need to interact with UIKit methods, they will
* probably need to call those on the main thread, as most of UIKit is main-
* thread-only. You can tell React Native to call your module methods on the
* main thread by returning a reference to the main queue, like this:
*
* - (dispatch_queue_t)methodQueue
* {
* return dispatch_get_main_queue();
* }
*
* If your methods perform heavy work such as synchronous filesystem or network
* access, you probably don't want to block the default background queue, as
* this will stall other methods. Instead, you should return a custom serial
* queue, like this:
*
* - (dispatch_queue_t)methodQueue
* {
* return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL);
* }
*
* Alternatively, if only some methods of the module should be executed on a
* particular queue you can leave this method unimplemented, and simply
* dispatch_async() to the required queue within the method itself.
*/
- (dispatch_queue_t)methodQueue;
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.ModuleName.X. This method is called when the module is

View File

@ -7,16 +7,14 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <objc/message.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import "../Layout/Layout.h"
#import "../Views/RCTAnimationType.h"
#import "../Views/RCTPointerEvents.h"
#import "Layout.h"
#import "RCTAnimationType.h"
#import "RCTDefines.h"
#import "RCTLog.h"
#import "RCTPointerEvents.h"
/**
* This class provides a collection of conversion functions for mapping
@ -116,33 +114,27 @@ typedef BOOL css_overflow;
@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);
RCT_EXTERN 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);
RCT_EXTERN BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
/**
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
* Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these.
*/
NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id);
#ifdef __cplusplus
}
#endif
RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id);
RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id);
RCT_EXTERN void RCTLogConvertError(id, const char *);
/**
* This macro is used for creating simple converter functions that just call
@ -157,18 +149,19 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
#define RCT_CUSTOM_CONVERTER(type, name, code) \
+ (type)name:(id)json \
{ \
if (json == [NSNull null]) { \
json = nil; \
} \
@try { \
json = (json == (id)kCFNull) ? nil : json; \
if (!RCT_DEBUG) { \
return code; \
} else { \
@try { \
return code; \
} \
@catch (__unused NSException *e) { \
RCTLogConvertError(json, #type); \
json = nil; \
return code; \
} \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
json, [json classForCoder], #type); \
json = nil; \
return code; \
} \
}
/**
@ -190,22 +183,14 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
dispatch_once(&onceToken, ^{ \
mapping = values; \
}); \
NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
return [RCTConvertEnumValue(#type, mapping, @(default), json) getter]; \
}
/**
* This macro is used for creating converter functions for typed arrays.
*/
#define RCT_ARRAY_CONVERTER(type) \
+ (type##Array *)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; \
#define RCT_ARRAY_CONVERTER(type) \
+ (NSArray *)type##Array:(id)json \
{ \
return RCTConvertArrayValue(@selector(type:), json); \
}

View File

@ -11,8 +11,16 @@
#import <objc/message.h>
#import "RCTDefines.h"
@implementation RCTConvert
void RCTLogConvertError(id json, const char *type)
{
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to %s",
json, [json classForCoder], type);
}
RCT_CONVERTER(BOOL, BOOL, boolValue)
RCT_NUMBER_CONVERTER(double, doubleValue)
RCT_NUMBER_CONVERTER(float, floatValue)
@ -58,6 +66,10 @@ RCT_CONVERTER(NSString *, NSString, description)
+ (NSURL *)NSURL:(id)json
{
if (!json || json == (id)kCFNull) {
return nil;
}
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
return nil;
@ -115,7 +127,7 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0
// JS standard for time zones is minutes.
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
{
if (!json || json == (id)kCFNull) {
return defaultValue;
@ -224,57 +236,52 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
}), UIBarStyleDefault, integerValue)
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
static void RCTConvertCGStructValue(const char *type, NSArray *fields, NSDictionary *aliases, CGFloat *result, id json)
{
NSUInteger count = fields.count;
if ([json isKindOfClass:[NSArray class]]) {
if (RCT_DEBUG && [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++) {
result[i] = [RCTConvert CGFloat:json[i]];
}
}
} else if ([json isKindOfClass:[NSDictionary class]]) {
if (aliases.count) {
json = [json mutableCopy];
for (NSString *alias in aliases) {
NSString *key = aliases[alias];
NSNumber *number = json[alias];
if (number) {
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, type, key);
((NSMutableDictionary *)json)[key] = number;
}
}
}
for (NSUInteger i = 0; i < count; i++) {
result[i] = [RCTConvert CGFloat:json[fields[i]]];
}
} else if (RCT_DEBUG && json && json != (id)kCFNull) {
RCTLogConvertError(json, type);
}
}
/**
* 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[alias]; \
if (number) { \
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, #type, key); \
((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 classForCoder], json); \
} \
return result; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
type result; \
return result; \
} \
#define RCT_CGSTRUCT_CONVERTER(type, values, aliases) \
+ (type)type:(id)json \
{ \
static NSArray *fields; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
}); \
type result; \
RCTConvertCGStructValue(#type, fields, aliases, (CGFloat *)&result, json); \
return result; \
}
RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
@ -521,9 +528,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
} 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
@ -541,10 +546,9 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
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 classForCoder], json);
}
else if (RCT_DEBUG && json && json != (id)kCFNull) {
RCTLogConvertError(json, "a color");
}
// Default color
@ -569,8 +573,12 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
// 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 classForCoder], json);
if (!json || json == (id)kCFNull) {
return nil;
}
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) {
RCTLogConvertError(json, "an image");
return nil;
}
@ -757,6 +765,29 @@ static BOOL RCTFontIsCondensed(UIFont *font)
return bestMatch;
}
NSArray *RCTConvertArrayValue(SEL type, id json)
{
__block BOOL copy = NO;
__block NSArray *values = json = [RCTConvert NSArray:json];
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, BOOL *stop) {
id value = ((id(*)(Class, SEL, id))objc_msgSend)([RCTConvert class], type, jsonValue);
if (copy) {
if (value) {
[(NSMutableArray *)values addObject:value];
}
} else if (value != jsonValue) {
// Converted value is different, so we'll need to copy the array
values = [[NSMutableArray alloc] initWithCapacity:values.count];
for (NSInteger i = 0; i < idx; i++) {
[(NSMutableArray *)values addObject:json[i]];
}
[(NSMutableArray *)values addObject:value];
copy = YES;
}
}];
return values;
}
RCT_ARRAY_CONVERTER(NSString)
RCT_ARRAY_CONVERTER(NSDictionary)
RCT_ARRAY_CONVERTER(NSURL)

55
React/Base/RCTDefines.h Normal file
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.
*/
#import <Foundation/Foundation.h>
/**
* Make global functions usable in C++
*/
#if defined(__cplusplus)
#define RCT_EXTERN extern "C" __attribute__((visibility("default")))
#else
#define RCT_EXTERN extern __attribute__((visibility("default")))
#endif
/**
* The RCT_DEBUG macro can be used to exclude error checking and logging code
* from release builds to improve performance and reduce binary size.
*/
#ifndef RCT_DEBUG
#if DEBUG
#define RCT_DEBUG 1
#else
#define RCT_DEBUG 0
#endif
#endif
/**
* The RCT_DEV macro can be used to enable or disable development tools
* such as the debug executors, dev menu, red box, etc.
*/
#ifndef RCT_DEV
#if DEBUG
#define RCT_DEV 1
#else
#define RCT_DEV 0
#endif
#endif
/**
* By default, only raise an NSAssertion in debug mode
* (custom assert functions will still be called).
*/
#ifndef RCT_NSASSERT
#if RCT_DEBUG
#define RCT_NSASSERT 1
#else
#define RCT_NSASSERT 0
#endif
#endif

View File

@ -9,11 +9,50 @@
#import <UIKit/UIKit.h>
@class RCTBridge;
#import "RCTBridge.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
@interface RCTDevMenu : NSObject
/**
* Developer menu, useful for exposing extra functionality when debugging.
*/
@interface RCTDevMenu : NSObject <RCTBridgeModule, RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
/**
* Is the menu enabled. The menu is enabled by default in debug mode, but
* you may wish to disable it so that you can provide your own shake handler.
*/
@property (nonatomic, assign) BOOL shakeToShow;
/**
* Enables performance profiling.
*/
@property (nonatomic, assign) BOOL profilingEnabled;
/**
* Enables automatic polling for JS code changes. Only applicable when
* running the app from a server.
*/
@property (nonatomic, assign) BOOL liveReloadEnabled;
/**
* The time between checks for code changes. Defaults to 1 second.
*/
@property (nonatomic, assign) NSTimeInterval liveReloadPeriod;
/**
* Manually show the menu. This will.
*/
- (void)show;
@end
/**
* This category makes the developer menu instance available via the
* RCTBridge, which is useful for any class that needs to access the menu.
*/
@interface RCTBridge (RCTDevMenu)
@property (nonatomic, readonly) RCTDevMenu *devMenu;
@end

View File

@ -9,101 +9,220 @@
#import "RCTDevMenu.h"
#import "RCTRedBox.h"
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#import "RCTRootView.h"
#import "RCTSourceCode.h"
#import "RCTWebViewExecutor.h"
#import "RCTUtils.h"
@interface RCTBridge (RCTDevMenu)
@property (nonatomic, copy, readonly) NSArray *profile;
@interface RCTBridge (Profiling)
- (void)startProfiling;
- (void)stopProfiling;
@end
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
@implementation UIWindow (RCTDevMenu)
- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
}
}
@end
@interface RCTDevMenu () <UIActionSheetDelegate>
@end
@implementation RCTDevMenu
{
BOOL _liveReload;
__weak RCTBridge *_bridge;
NSTimer *_updateTimer;
UIActionSheet *_actionSheet;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
+ (void)initialize
{
if (self = [super init]) {
_bridge = bridge;
// We're swizzling here because it's poor form to override methods in a category,
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
// no need to call the original implementation.
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
}
- (instancetype)init
{
if ((self = [super init])) {
_shakeToShow = YES;
_liveReloadPeriod = 1.0; // 1 second
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(showOnShake)
name:RCTShowDevMenuNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)showOnShake
{
if (_shakeToShow) {
[self show];
}
}
- (void)show
{
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling";
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
if (_actionSheet) {
return;
}
NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
[_bridge reload];
} else if (buttonIndex == 1) {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
} else if (buttonIndex == 2) {
Class cls = [RCTWebViewExecutor class];
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
} else if (buttonIndex == 3) {
_liveReload = !_liveReload;
[self _pollAndReload];
} else if (buttonIndex == 4) {
if (_bridge.profile) {
[_bridge stopProfiling];
} else {
[_bridge startProfiling];
}
}
}
_actionSheet = nil;
- (void)_pollAndReload
{
if (_liveReload) {
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
NSURL *url = sourceCodeModule.scriptURL;
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
}
}
- (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];
switch (buttonIndex) {
case 0: {
[_bridge reload];
break;
}
[self _pollAndReload];
});
case 1: {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
break;
}
case 2: {
Class cls = NSClassFromString(@"RCTWebViewExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
break;
}
case 3: {
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 4: {
self.profilingEnabled = !_profilingEnabled;
break;
}
default:
break;
}
}
- (void)setProfilingEnabled:(BOOL)enabled
{
if (_profilingEnabled == enabled) {
return;
}
_profilingEnabled = enabled;
if (RCTProfileIsProfiling()) {
[_bridge stopProfiling];
} else {
[_bridge startProfiling];
}
}
- (void)setLiveReloadEnabled:(BOOL)enabled
{
if (_liveReloadEnabled == enabled) {
return;
}
_liveReloadEnabled = enabled;
if (_liveReloadEnabled) {
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
target:self
selector:@selector(pollForUpdates)
userInfo:nil
repeats:YES];
} else {
[_updateTimer invalidate];
_updateTimer = nil;
}
}
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
{
_liveReloadPeriod = liveReloadPeriod;
if (_liveReloadEnabled) {
self.liveReloadEnabled = NO;
self.liveReloadEnabled = YES;
}
}
- (void)pollForUpdates
{
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
if (!sourceCodeModule) {
RCTLogError(@"RCTSourceCode module not found");
self.liveReloadEnabled = NO;
}
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
[_bridge reload];
}
}];
}
- (BOOL)isValid
{
return !_liveReloadEnabled || _updateTimer != nil;
}
- (void)invalidate
{
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
[_updateTimer invalidate];
_updateTimer = nil;
}
@end
@implementation RCTBridge (RCTDevMenu)
- (RCTDevMenu *)devMenu
{
return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])];
}
@end

View File

@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <objc/runtime.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import "RCTInvalidating.h"
@ -27,6 +29,7 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
- (void)executeJSCall:(NSString *)name
method:(NSString *)method
arguments:(NSArray *)arguments
context:(NSNumber *)executorID
callback:(RCTJavaScriptCallback)onComplete;
/**
@ -39,4 +42,27 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
- (void)injectJSONText:(NSString *)script
asGlobalObjectNamed:(NSString *)objectName
callback:(RCTJavaScriptCompleteBlock)onComplete;
/**
* Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async`
* on the main queue if the executor doesn't own a thread.
*/
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
@end
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass)
{
static NSUInteger executorID = 0;
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
if (executor) {
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
}
return executor;
}
__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)
{
return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0;
}

View File

@ -1,4 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* 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>

View File

@ -1,4 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* 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 "RCTJavaScriptLoader.h"
@ -78,8 +85,9 @@
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root",
NSLocalizedDescriptionKey: desc,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
@ -133,9 +141,9 @@
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *_error) {
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(_error);
onComplete(scriptError);
});
}];
}];

View File

@ -10,10 +10,7 @@
#import <Foundation/Foundation.h>
#import "RCTAssert.h"
#ifdef __cplusplus
extern "C" {
#endif
#import "RCTDefines.h"
/**
* Thresholds for logs to raise an assertion, or display redbox, respectively.
@ -45,11 +42,16 @@ typedef void (^RCTLogFunction)(
NSString *message
);
/**
* Get a given thread's name (or the current queue, if in debug mode)
*/
RCT_EXTERN NSString *RCTThreadName(NSThread *);
/**
* A method to generate a string from a collection of log data. To omit any
* particular data from the log, just pass nil or zero for the argument.
*/
NSString *RCTFormatLog(
RCT_EXTERN NSString *RCTFormatLog(
NSDate *timestamp,
NSThread *thread,
RCTLogLevel level,
@ -68,35 +70,35 @@ extern RCTLogFunction RCTDefaultLogFunction;
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
* RCTLogLevelError for production.
*/
void RCTSetLogThreshold(RCTLogLevel threshold);
RCTLogLevel RCTGetLogThreshold(void);
RCT_EXTERN void RCTSetLogThreshold(RCTLogLevel threshold);
RCT_EXTERN RCTLogLevel RCTGetLogThreshold(void);
/**
* These methods get and set the current logging function called by the RCTLogXX
* macros. You can use these to replace the standard behavior with custom log
* functionality.
*/
void RCTSetLogFunction(RCTLogFunction logFunction);
RCTLogFunction RCTGetLogFunction(void);
RCT_EXTERN void RCTSetLogFunction(RCTLogFunction logFunction);
RCT_EXTERN RCTLogFunction RCTGetLogFunction(void);
/**
* This appends additional code to the existing log function, without replacing
* the existing functionality. Useful if you just want to forward logs to an
* extra service without changing the default behavior.
*/
void RCTAddLogFunction(RCTLogFunction logFunction);
RCT_EXTERN void RCTAddLogFunction(RCTLogFunction logFunction);
/**
* This method adds a conditional prefix to any messages logged within the scope
* of the passed block. This is useful for adding additional context to log
* messages. The block will be performed synchronously on the current thread.
*/
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
RCT_EXTERN void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
/**
* Private logging functions - ignore these.
*/
void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
RCT_EXTERN void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
#define _RCTLog(lvl, ...) do { \
if (lvl >= RCTLOG_FATAL_LEVEL) { RCTAssert(NO, __VA_ARGS__); } \
_RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \
@ -111,7 +113,3 @@ void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FU
#define RCTLogWarn(...) _RCTLog(RCTLogLevelWarning, __VA_ARGS__)
#define RCTLogError(...) _RCTLog(RCTLogLevelError, __VA_ARGS__)
#define RCTLogMustFix(...) _RCTLog(RCTLogLevelMustFix, __VA_ARGS__)
#ifdef __cplusplus
}
#endif

View File

@ -11,6 +11,7 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTDefines.h"
#import "RCTRedBox.h"
@interface RCTBridge (Logging)
@ -31,12 +32,12 @@ const char *RCTLogLevels[] = {
static RCTLogFunction RCTCurrentLogFunction;
static RCTLogLevel RCTCurrentLogThreshold;
void RCTLogSetup(void) __attribute__((constructor));
void RCTLogSetup()
__attribute__((constructor))
static void RCTLogSetup()
{
RCTCurrentLogFunction = RCTDefaultLogFunction;
#if DEBUG
#if RCT_DEBUG
RCTCurrentLogThreshold = RCTLogLevelInfo - 1;
#else
RCTCurrentLogThreshold = RCTLogLevelError;
@ -98,6 +99,22 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
[prefixStack removeLastObject];
}
NSString *RCTThreadName(NSThread *thread)
{
NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
if (threadName.length == 0) {
#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
#pragma clang diagnostic pop
#else
threadName = [NSString stringWithFormat:@"%p", thread];
#endif
}
return threadName;
}
NSString *RCTFormatLog(
NSDate *timestamp,
NSThread *thread,
@ -121,18 +138,7 @@ NSString *RCTFormatLog(
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
}
if (thread) {
NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
if (threadName.length == 0) {
#if DEBUG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
#pragma clang diagnostic pop
#else
threadName = [NSString stringWithFormat:@"%p", thread];
#endif
}
[log appendFormat:@"[tid:%@]", threadName];
[log appendFormat:@"[tid:%@]", RCTThreadName(thread)];
}
if (fileName) {
fileName = [fileName lastPathComponent];
@ -156,12 +162,7 @@ void _RCTLogFormat(
NSString *format, ...)
{
#if DEBUG
BOOL log = YES;
#else
BOOL log = (RCTCurrentLogFunction != nil);
#endif
BOOL log = RCT_DEBUG || (RCTCurrentLogFunction != nil);
if (log && level >= RCTCurrentLogThreshold) {
// Get message
@ -183,15 +184,15 @@ void _RCTLogFormat(
level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message
);
#if DEBUG
#if RCT_DEBUG // Red box is only available in debug mode
// Log to red box
if (level >= RCTLOG_REDBOX_LEVEL) {
[[RCTRedBox sharedInstance] showErrorMessage:message];
}
// Log to red box
if (level >= RCTLOG_REDBOX_LEVEL) {
[[RCTRedBox sharedInstance] showErrorMessage:message];
}
// Log to JS executor
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
// Log to JS executor
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
#endif

105
React/Base/RCTProfile.h Normal file
View File

@ -0,0 +1,105 @@
/**
* 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 "RCTDefines.h"
/**
* RCTProfile
*
* This file provides a set of functions and macros for performance profiling
*
* NOTE: This API is a work in a work in progress, please consider carefully
* before before using it.
*/
#if RCT_DEV
/**
* Returns YES if the profiling information is currently being collected
*/
RCT_EXTERN BOOL RCTProfileIsProfiling(void);
/**
* Start collecting profiling information
*/
RCT_EXTERN void RCTProfileInit(void);
/**
* Stop profiling and return a JSON string of the collected data - The data
* returned is compliant with google's trace event format - the format used
* as input to trace-viewer
*/
RCT_EXTERN NSString *RCTProfileEnd(void);
/**
* Collects the initial event information for the event and returns a reference ID
*/
RCT_EXTERN NSNumber *_RCTProfileBeginEvent(void);
/**
* The ID returned by BeginEvent should then be passed into EndEvent, with the
* rest of the event information. Just at this point the event will actually be
* registered
*/
RCT_EXTERN void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id);
/**
* This pair of macros implicitly handle the event ID when beginning and ending
* an event, for both simplicity and performance reasons, this method is preferred
*
* NOTE: The EndEvent call has to be either, in the same scope of BeginEvent,
* or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible
* for EndEvent, in this case you may want to use the actual C functions.
*/
#define RCTProfileBeginEvent() \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
NSNumber *__rct_profile_id = _RCTProfileBeginEvent(); \
_Pragma("clang diagnostic pop")
#define RCTProfileEndEvent(name, category, args...) \
_RCTProfileEndEvent(__rct_profile_id, name, category, args)
/**
* An event that doesn't have a duration (i.e. Notification, VSync, etc)
*/
RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *);
/**
* Helper to profile the duration of the execution of a block. This method uses
* self and _cmd to name this event for simplicity sake.
*
* NOTE: The block can't expect any argument
*/
#define RCTProfileBlock(block, category, arguments) \
^{ \
RCTProfileBeginEvent(); \
block(); \
RCTProfileEndEvent([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)], category, arguments); \
}
#else
#define RCTProfileIsProfiling(...) NO
#define RCTProfileInit(...)
#define RCTProfileEnd(...) @""
#define _RCTProfileBeginEvent(...) @0
#define RCTProfileBeginEvent(...)
#define _RCTProfileEndEvent(...)
#define RCTProfileEndEvent(...)
#define RCTProfileImmediateEvent(...)
#define RCTProfileBlock(block, ...) block
#endif

174
React/Base/RCTProfile.m Normal file
View File

@ -0,0 +1,174 @@
/**
* 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 "RCTProfile.h"
#import <mach/mach.h>
#import <UIKit/UIKit.h>
#import "RCTDefines.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#if RCT_DEV
#pragma mark - Prototypes
NSNumber *RCTProfileTimestamp(NSTimeInterval);
NSString *RCTProfileMemory(vm_size_t);
NSDictionary *RCTProfileGetMemoryUsage(void);
#pragma mark - Constants
NSString const *RCTProfileTraceEvents = @"traceEvents";
NSString const *RCTProfileSamples = @"samples";
#pragma mark - Variables
NSDictionary *RCTProfileInfo;
NSUInteger RCTProfileEventID = 0;
NSMutableDictionary *RCTProfileOngoingEvents;
NSTimeInterval RCTProfileStartTime;
NSLock *_RCTProfileLock;
#pragma mark - Macros
#define RCTProfileAddEvent(type, props...) \
[RCTProfileInfo[type] addObject:@{ \
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
@"tid": RCTThreadName([NSThread currentThread]), \
props \
}];
#define CHECK(...) \
if (!RCTProfileIsProfiling()) { \
return __VA_ARGS__; \
}
#define RCTProfileLock(...) \
[_RCTProfileLock lock]; \
__VA_ARGS__ \
[_RCTProfileLock unlock]
#pragma mark - Private Helpers
NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp)
{
return @((timestamp - RCTProfileStartTime) * 1e6);
}
NSString *RCTProfileMemory(vm_size_t memory)
{
double mem = ((double)memory) / 1024 / 1024;
return [NSString stringWithFormat:@"%.2lfmb", mem];
}
NSDictionary *RCTProfileGetMemoryUsage(void)
{
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&info,
&size);
if( kerr == KERN_SUCCESS ) {
return @{
@"suspend_count": @(info.suspend_count),
@"virtual_size": RCTProfileMemory(info.virtual_size),
@"resident_size": RCTProfileMemory(info.resident_size),
};
} else {
return @{};
}
}
#pragma mark - Public Functions
BOOL RCTProfileIsProfiling(void)
{
RCTProfileLock(
BOOL profiling = RCTProfileInfo != nil;
);
return profiling;
}
void RCTProfileInit(void)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_RCTProfileLock = [[NSLock alloc] init];
});
RCTProfileLock(
RCTProfileStartTime = CACurrentMediaTime();
RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init];
RCTProfileInfo = @{
RCTProfileTraceEvents: [[NSMutableArray alloc] init],
RCTProfileSamples: [[NSMutableArray alloc] init],
};
);
}
NSString *RCTProfileEnd(void)
{
RCTProfileLock(
NSString *log = RCTJSONStringify(RCTProfileInfo, NULL);
RCTProfileEventID = 0;
RCTProfileInfo = nil;
RCTProfileOngoingEvents = nil;
);
return log;
}
NSNumber *_RCTProfileBeginEvent(void)
{
CHECK(@0);
RCTProfileLock(
NSNumber *eventID = @(++RCTProfileEventID);
RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime());
);
return eventID;
}
void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args)
{
CHECK();
RCTProfileLock(
NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID];
if (startTimestamp) {
NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime());
RCTProfileAddEvent(RCTProfileTraceEvents,
@"name": name,
@"cat": categories,
@"ph": @"X",
@"ts": startTimestamp,
@"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue),
@"args": args ?: @[],
);
[RCTProfileOngoingEvents removeObjectForKey:eventID];
}
);
}
void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope)
{
CHECK();
RCTProfileLock(
RCTProfileAddEvent(RCTProfileTraceEvents,
@"name": name,
@"ts": RCTProfileTimestamp(timestamp),
@"scope": scope,
@"ph": @"i",
@"args": RCTProfileGetMemoryUsage(),
);
);
}
#endif

View File

@ -7,6 +7,10 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDefines.h"
#if RCT_DEBUG // Red box is only available in debug mode
#import <UIKit/UIKit.h>
@interface RCTRedBox : NSObject
@ -23,3 +27,5 @@
- (void)dismiss;
@end
#endif

View File

@ -7,6 +7,10 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDefines.h"
#if RCT_DEBUG // Red box is only available in debug mode
#import "RCTRedBox.h"
#import "RCTBridge.h"
@ -282,23 +286,12 @@
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
{
#if DEBUG
dispatch_block_t block = ^{
dispatch_async(dispatch_get_main_queue(), ^{
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
@ -316,3 +309,5 @@
}
@end
#endif

View File

@ -57,12 +57,6 @@
*/
@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;
/**
* The backing view controller of the root view.
*/

View File

@ -13,7 +13,6 @@
#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTDevMenu.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
@ -42,7 +41,6 @@
@implementation RCTRootView
{
RCTDevMenu *_devMenu;
RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
NSString *_moduleName;
@ -60,12 +58,6 @@
self.backgroundColor = [UIColor whiteColor];
#ifdef DEBUG
_enableDevMenu = YES;
#endif
_bridge = bridge;
_moduleName = moduleName;
@ -120,18 +112,6 @@
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
if (!_devMenu) {
_devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge];
}
[_devMenu show];
} else {
[super motionEnded:motion withEvent:event];
}
}
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
@ -169,7 +149,7 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
[super layoutSubviews];
if (_contentView) {
_contentView.frame = self.bounds;
[_bridge.uiManager setFrame:self.frame forRootView:_contentView];
[_bridge.uiManager setFrame:self.bounds forRootView:_contentView];
}
}

Some files were not shown because too many files have changed in this diff Show More