From 0686b0147c8c8084e4a226b7ea04585362eccea8 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 9 Apr 2015 08:46:53 -0700 Subject: [PATCH] Updates from Thu 9 Apr - [React Native] Fix RCTText crashes | Alex Akers - Ensure that NSLocationWhenInUseUsageDescription is set, throw error if not | Alex Kotliarskyi - [ReactNative] fix exception handler method name | Spencer Ahrens - [ReactNative] Re-configure horizontal swipe animations | Eric Vicenti - [ReactNative] : apply the fontWeight correctly if fontFamily style is also present | Kevin Gozali - [MAdMan] Dimensions.get('window') considered harmful | Philipp von Weitershausen - Navigator: Changed transitioner background color to 'transparent' | Eric Vicenti - [react-native] Listen on all IPv6 interfaces | Ben Alpert - [react-packager] Don't depend on error.stack being available | Amjad Masad - [ReactNative] fixup AnimationExperimental a bit | Spencer Ahrens - [react-packager] Implement new style asset packaging (with dimensions) | Amjad Masad - [React Native] RCT_EXPORT lvl.2 | Alex Akers - [react_native] Implement TextInput end editing | Andrei Coman - [react_native] Make TextInput focus, blur, dismiss and show keyboard work | Andrei Coman - Added non-class-scanning-based approach fror registering js methods | Nick Lockwood - [ReactNative] Update package.json | Christopher Chedeau - [ReactNative] Do flow check when running packager | Spencer Ahrens - [ReactNative] Fix typo/bug in Navigator._completeTransition | Eric Vicenti - [ReactNative] Fix Navigator exception when touching during transition | Eric Vicenti - [ReactNative] Remove bridge retaining cycles | Tadeu Zagallo - [ReactNative] Fix and re-add WebView executor | Tadeu Zagallo --- Examples/2048/Game2048.js | 26 +- .../SampleApp.xcodeproj/project.pbxproj | 2 - Examples/SampleApp/iOS/Info.plist | 2 + .../UIExplorer.xcodeproj/project.pbxproj | 4 - .../xcschemes/UIExplorer.xcscheme | 6 +- .../ActionSheetIOS/RCTActionSheetManager.m | 25 +- Libraries/AdSupport/RCTAdSupport.m | 14 +- Libraries/Animation/AnimationExperimental.js | 63 ++- .../Animation/AnimationExperimentalMixin.js | 58 --- Libraries/Animation/AnimationUtils.js | 62 +-- Libraries/Animation/LayoutAnimation.js | 14 +- .../RCTAnimationExperimentalManager.m | 144 ++++-- Libraries/Components/TextInput/TextInput.js | 16 +- .../Components/Touchable/TouchableBounce.js | 26 +- .../CustomComponents/Navigator/Navigator.js | 17 +- .../Navigator/NavigatorSceneConfigs.js | 29 +- Libraries/Geolocation/Geolocation.ios.js | 8 +- Libraries/Geolocation/RCTLocationObserver.m | 25 +- Libraries/Image/RCTCameraRollManager.m | 16 +- Libraries/Image/RCTNetworkImageViewManager.m | 2 + Libraries/Image/RCTStaticImageManager.m | 2 + Libraries/LinkingIOS/RCTLinkingManager.m | 16 +- Libraries/Network/RCTDataManager.m | 12 +- Libraries/Network/RCTReachability.m | 8 +- .../RCTPushNotificationManager.m | 19 +- Libraries/RCTTest/RCTTestModule.m | 13 +- .../RCTWebSocketExecutor.m | 2 +- Libraries/RCTWebSocketDebugger/SRWebSocket.m | 9 +- Libraries/Text/RCTRawTextManager.m | 2 + Libraries/Text/RCTText.m | 29 +- Libraries/Text/RCTTextManager.m | 2 + Libraries/Vibration/RCTVibration.m | 5 +- React/Base/RCTBridge.h | 34 +- React/Base/RCTBridge.m | 437 ++++++++++++------ React/Base/RCTBridgeModule.h | 55 ++- React/Base/RCTConvert.h | 28 +- React/Base/RCTConvert.m | 12 +- React/Base/RCTDevMenu.m | 15 +- React/Base/RCTEventDispatcher.m | 11 +- React/Base/RCTJavaScriptLoader.m | 23 +- React/Base/RCTLog.h | 10 +- React/Base/RCTLog.m | 20 +- React/Base/RCTRedBox.m | 3 +- React/Base/RCTRootView.m | 15 +- React/Base/RCTTouchHandler.m | 5 +- React/Base/RCTUtils.h | 3 - React/Base/RCTUtils.m | 25 - React/Executors/RCTWebViewExecutor.m | 2 + React/Modules/RCTAlertManager.m | 7 +- React/Modules/RCTAppState.m | 8 +- React/Modules/RCTAsyncLocalStorage.m | 25 +- React/Modules/RCTExceptionsManager.m | 12 +- React/Modules/RCTSourceCode.m | 6 +- React/Modules/RCTStatusBarManager.m | 12 +- React/Modules/RCTTiming.m | 34 +- React/Modules/RCTUIManager.m | 141 +++--- React/React.xcodeproj/project.pbxproj | 4 +- React/Views/RCTDatePickerManager.m | 2 + React/Views/RCTMapManager.m | 2 + React/Views/RCTNavItemManager.m | 2 + React/Views/RCTNavigatorManager.m | 10 +- React/Views/RCTPickerManager.m | 2 + React/Views/RCTScrollViewManager.m | 8 +- React/Views/RCTSliderManager.m | 2 + React/Views/RCTSwitchManager.m | 2 + React/Views/RCTTabBarItemManager.m | 2 + React/Views/RCTTabBarManager.m | 2 + React/Views/RCTTextFieldManager.m | 2 + .../Views/RCTUIActivityIndicatorViewManager.m | 2 + React/Views/RCTViewManager.h | 8 - React/Views/RCTViewManager.m | 13 +- React/Views/RCTWebViewManager.m | 14 +- package.json | 5 +- packager/getFlowTypeCheckMiddleware.js | 86 ++++ packager/packager.js | 8 +- .../DependencyResolver/ModuleDescriptor.js | 5 + .../__tests__/DependencyGraph-test.js | 6 +- .../haste/DependencyGraph/index.js | 2 +- .../react-packager/src/JSTransformer/index.js | 2 +- .../src/Packager/__tests__/Packager-test.js | 35 +- packager/react-packager/src/Packager/index.js | 49 +- packager/react-packager/src/Server/index.js | 6 + 82 files changed, 1166 insertions(+), 736 deletions(-) delete mode 100644 Libraries/Animation/AnimationExperimentalMixin.js create mode 100644 packager/getFlowTypeCheckMiddleware.js diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index a6e12ff13..a6e041ceb 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -73,19 +73,29 @@ class Tile extends React.Component { if (tile.isNew()) { offset.opacity = 0; } else { - var point = [ - animationPosition(tile.toColumn()), - animationPosition(tile.toRow()), - ]; - AnimationExperimental.startAnimation(this.refs['this'], 100, 0, 'easeInOutQuad', {position: point}); + var point = { + x: animationPosition(tile.toColumn()), + y: animationPosition(tile.toRow()), + }; + AnimationExperimental.startAnimation({ + node: this.refs['this'], + duration: 100, + easing: 'easeInOutQuad', + property: 'position', + toValue: point, + }); } - return offset; } - componentDidMount() { - AnimationExperimental.startAnimation(this.refs['this'], 100, 0, 'easeInOutQuad', {opacity: 1}); + AnimationExperimental.startAnimation({ + node: this.refs['this'], + duration: 100, + easing: 'easeInOutQuad', + property: 'opacity', + toValue: 1, + }); } render() { diff --git a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj index 5b716c3c0..2ea052a2e 100644 --- a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj +++ b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 00481BE81AC0C86700671115 /* libRCTWebSocketDebugger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00481BE61AC0C7FA00671115 /* libRCTWebSocketDebugger.a */; }; - 00481BEA1AC0C89D00671115 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 00481BE91AC0C89D00671115 /* libicucore.dylib */; }; 008F07F31AC5B25A0029DE68 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 008F07F21AC5B25A0029DE68 /* main.jsbundle */; }; 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; 00C302E61ABCBA2D00DB3ED1 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302B41ABCB8E700DB3ED1 /* libRCTAdSupport.a */; }; @@ -116,7 +115,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 00481BEA1AC0C89D00671115 /* libicucore.dylib in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, 00481BE81AC0C86700671115 /* libRCTWebSocketDebugger.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, diff --git a/Examples/SampleApp/iOS/Info.plist b/Examples/SampleApp/iOS/Info.plist index c6d2494cc..0a56a6ea8 100644 --- a/Examples/SampleApp/iOS/Info.plist +++ b/Examples/SampleApp/iOS/Info.plist @@ -36,5 +36,7 @@ UIViewControllerBasedStatusBarAppearance + NSLocationWhenInUseUsageDescription + diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 030e2576c..348d04f0d 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* UIExplorerTests.m */; }; 00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D277131AB8C2C700DC1E48 /* libRCTWebSocketDebugger.a */; }; - 00D2771C1AB8C55500DC1E48 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D2771B1AB8C55500DC1E48 /* libicucore.dylib */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; @@ -118,7 +117,6 @@ 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 004D28A21AAF61C70097A701 /* UIExplorerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIExplorerTests.m; sourceTree = ""; }; 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocketDebugger.xcodeproj; path = ../../Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj; sourceTree = ""; }; - 00D2771B1AB8C55500DC1E48 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; @@ -150,7 +148,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 00D2771C1AB8C55500DC1E48 /* libicucore.dylib in Frameworks */, 14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */, 00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */, 58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */, @@ -207,7 +204,6 @@ 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, 00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */, D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */, - 00D2771B1AB8C55500DC1E48 /* libicucore.dylib */, ); name = Libraries; sourceTree = ""; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme index 2189d2d0e..b231b77ee 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme @@ -72,7 +72,8 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" allowLocationSimulation = "YES"> - + - + +@interface RCTActionSheetManager () @end -@implementation RCTActionSheetManager { +@implementation RCTActionSheetManager +{ NSMutableDictionary *_callbacks; } +RCT_EXPORT_MODULE() + - (instancetype)init { if ((self = [super init])) { @@ -27,12 +30,10 @@ return self; } -- (void)showActionSheetWithOptions:(NSDictionary *)options - failureCallback:(RCTResponseSenderBlock)failureCallback - successCallback:(RCTResponseSenderBlock)successCallback +RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options + failureCallback:(RCTResponseSenderBlock)failureCallback + successCallback:(RCTResponseSenderBlock)successCallback) { - RCT_EXPORT(); - dispatch_async(dispatch_get_main_queue(), ^{ UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; @@ -62,12 +63,10 @@ }); } -- (void)showShareActionSheetWithOptions:(NSDictionary *)options - failureCallback:(RCTResponseSenderBlock)failureCallback - successCallback:(RCTResponseSenderBlock)successCallback +RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options + failureCallback:(RCTResponseSenderBlock)failureCallback + successCallback:(RCTResponseSenderBlock)successCallback) { - RCT_EXPORT(); - dispatch_async(dispatch_get_main_queue(), ^{ NSMutableArray *items = [NSMutableArray array]; id message = options[@"message"]; @@ -134,7 +133,7 @@ #pragma mark Private -static NSString *keyForInstance(id instance) +NS_INLINE NSString *keyForInstance(id instance) { return [NSString stringWithFormat:@"%p", instance]; } diff --git a/Libraries/AdSupport/RCTAdSupport.m b/Libraries/AdSupport/RCTAdSupport.m index e6be96f6c..1b2ad92de 100644 --- a/Libraries/AdSupport/RCTAdSupport.m +++ b/Libraries/AdSupport/RCTAdSupport.m @@ -11,10 +11,11 @@ @implementation RCTAdSupport -- (void)getAdvertisingId:(RCTResponseSenderBlock)callback withErrorCallback:(RCTResponseSenderBlock)errorCallback -{ - RCT_EXPORT(); +RCT_EXPORT_MODULE() +RCT_EXPORT_METHOD(getAdvertisingId:(RCTResponseSenderBlock)callback + withErrorCallback:(RCTResponseSenderBlock)errorCallback) +{ if ([ASIdentifierManager class]) { callback(@[[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]]); } else { @@ -22,12 +23,11 @@ } } -- (void)getAdvertisingTrackingEnabled:(RCTResponseSenderBlock)callback withErrorCallback:(RCTResponseSenderBlock)errorCallback +RCT_EXPORT_METHOD(getAdvertisingTrackingEnabled:(RCTResponseSenderBlock)callback + withErrorCallback:(RCTResponseSenderBlock)errorCallback) { - RCT_EXPORT(); - if ([ASIdentifierManager class]) { - bool hasTracking = [[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]; + BOOL hasTracking = [[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]; callback(@[@(hasTracking)]); } else { return errorCallback(@[@"as_identifier_unavailable"]); diff --git a/Libraries/Animation/AnimationExperimental.js b/Libraries/Animation/AnimationExperimental.js index 79daa4550..0a32c3f44 100644 --- a/Libraries/Animation/AnimationExperimental.js +++ b/Libraries/Animation/AnimationExperimental.js @@ -16,6 +16,17 @@ var AnimationUtils = require('AnimationUtils'); type EasingFunction = (t: number) => number; +var Properties = { + opacity: true, + position: true, + positionX: true, + positionY: true, + rotation: true, + scaleXY: true, +}; + +type ValueType = number | Array | {[key: string]: number}; + /** * This is an experimental module that is under development, incomplete, * potentially buggy, not used in any production apps, and will probably change @@ -24,24 +35,34 @@ type EasingFunction = (t: number) => number; * Use at your own risk. */ var AnimationExperimental = { - Mixin: require('AnimationExperimentalMixin'), - startAnimation: function( - node: any, - duration: number, - delay: number, - easing: (string | EasingFunction), - properties: {[key: string]: any} + anim: { + node: any; + duration: number; + easing: ($Enum | EasingFunction); + property: $Enum; + toValue: ValueType; + fromValue?: ValueType; + delay?: number; + }, + callback?: ?(finished: bool) => void ): number { - var nodeHandle = +node.getNodeHandle(); - var easingSample = AnimationUtils.evaluateEasingFunction(duration, easing); - var tag: number = RCTAnimationManager.startAnimation( + var nodeHandle = anim.node.getNodeHandle(); + var easingSample = AnimationUtils.evaluateEasingFunction( + anim.duration, + anim.easing + ); + var tag: number = AnimationUtils.allocateTag(); + var props = {}; + props[anim.property] = {to: anim.toValue}; + RCTAnimationManager.startAnimation( nodeHandle, - AnimationUtils.allocateTag(), - duration, - delay, + tag, + anim.duration, + anim.delay, easingSample, - properties + props, + callback ); return tag; }, @@ -51,4 +72,18 @@ var AnimationExperimental = { }, }; +if (__DEV__) { + if (RCTAnimationManager && RCTAnimationManager.Properties) { + var a = Object.keys(Properties); + var b = RCTAnimationManager.Properties; + var diff = a.filter((i) => b.indexOf(i) < 0).concat( + b.filter((i) => a.indexOf(i) < 0) + ); + if (diff.length > 0) { + throw new Error('JS animation properties don\'t match native properties.' + + JSON.stringify(diff, null, ' ')); + } + } +} + module.exports = AnimationExperimental; diff --git a/Libraries/Animation/AnimationExperimentalMixin.js b/Libraries/Animation/AnimationExperimentalMixin.js deleted file mode 100644 index 7cee9b72b..000000000 --- a/Libraries/Animation/AnimationExperimentalMixin.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * 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 AnimationExperimentalMixin - * @flow - */ -'use strict'; - -var AnimationUtils = require('AnimationUtils'); -var RCTAnimationManager = require('NativeModules').AnimationExperimentalManager; - -var invariant = require('invariant'); - -type EasingFunction = (t: number) => number; - -/** - * This is an experimental module that is under development, incomplete, - * potentially buggy, not used in any production apps, and will probably change - * in non-backward compatible ways. - * - * Use at your own risk. - */ -var AnimationExperimentalMixin = { - getInitialState: function(): Object { - return {}; - }, - - startAnimation: function( - refKey: string, - duration: number, - delay: number, - easing: (string | EasingFunction), - properties: {[key: string]: any} - ): number { - var ref = this.refs[refKey]; - invariant( - ref, - 'Invalid refKey ' + refKey + '; ' + - 'valid refs: ' + JSON.stringify(Object.keys(this.refs)) - ); - - var nodeHandle = +ref.getNodeHandle(); - var easingSample = AnimationUtils.evaluateEasingFunction(duration, easing); - var tag: number = RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties); - return tag; - }, - - stopAnimation: function(tag: number) { - RCTAnimationManager.stopAnimation(tag); - }, -}; - -module.exports = AnimationExperimentalMixin; diff --git a/Libraries/Animation/AnimationUtils.js b/Libraries/Animation/AnimationUtils.js index ae9be5ccf..d6d95f62d 100644 --- a/Libraries/Animation/AnimationUtils.js +++ b/Libraries/Animation/AnimationUtils.js @@ -20,27 +20,27 @@ type EasingFunction = (t: number) => number; var defaults = { - easeInQuad: function(t) { + easeInQuad: function(t: number): number { return t * t; }, - easeOutQuad: function(t) { + easeOutQuad: function(t: number): number { return -t * (t - 2); }, - easeInOutQuad: function(t) { + easeInOutQuad: function(t: number): number { t = t * 2; if (t < 1) { return 0.5 * t * t; } return -((t - 1) * (t - 3) - 1) / 2; }, - easeInCubic: function(t) { + easeInCubic: function(t: number): number { return t * t * t; }, - easeOutCubic: function(t) { + easeOutCubic: function(t: number): number { t -= 1; return t * t * t + 1; }, - easeInOutCubic: function(t) { + easeInOutCubic: function(t: number): number { t *= 2; if (t < 1) { return 0.5 * t * t * t; @@ -48,14 +48,14 @@ var defaults = { t -= 2; return (t * t * t + 2) / 2; }, - easeInQuart: function(t) { + easeInQuart: function(t: number): number { return t * t * t * t; }, - easeOutQuart: function(t) { + easeOutQuart: function(t: number): number { t -= 1; return -(t * t * t * t - 1); }, - easeInOutQuart: function(t) { + easeInOutQuart: function(t: number): number { t *= 2; if (t < 1) { return 0.5 * t * t * t * t; @@ -63,14 +63,14 @@ var defaults = { t -= 2; return -(t * t * t * t - 2) / 2; }, - easeInQuint: function(t) { + easeInQuint: function(t: number): number { return t * t * t * t * t; }, - easeOutQuint: function(t) { + easeOutQuint: function(t: number): number { t -= 1; return t * t * t * t * t + 1; }, - easeInOutQuint: function(t) { + easeInOutQuint: function(t: number): number { t *= 2; if (t < 1) { return (t * t * t * t * t) / 2; @@ -78,22 +78,22 @@ var defaults = { t -= 2; return (t * t * t * t * t + 2) / 2; }, - easeInSine: function(t) { + easeInSine: function(t: number): number { return -Math.cos(t * (Math.PI / 2)) + 1; }, - easeOutSine: function(t) { + easeOutSine: function(t: number): number { return Math.sin(t * (Math.PI / 2)); }, - easeInOutSine: function(t) { + easeInOutSine: function(t: number): number { return -(Math.cos(Math.PI * t) - 1) / 2; }, - easeInExpo: function(t) { + easeInExpo: function(t: number): number { return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); }, - easeOutExpo: function(t) { + easeOutExpo: function(t: number): number { return (t === 1) ? 1 : (-Math.pow(2, -10 * t) + 1); }, - easeInOutExpo: function(t) { + easeInOutExpo: function(t: number): number { if (t === 0) { return 0; } @@ -106,14 +106,14 @@ var defaults = { } return (-Math.pow(2, -10 * (t - 1)) + 2) / 2; }, - easeInCirc: function(t) { + easeInCirc: function(t: number): number { return -(Math.sqrt(1 - t * t) - 1); }, - easeOutCirc: function(t) { + easeOutCirc: function(t: number): number { t -= 1; return Math.sqrt(1 - t * t); }, - easeInOutCirc: function(t) { + easeInOutCirc: function(t: number): number { t *= 2; if (t < 1) { return -(Math.sqrt(1 - t * t) - 1) / 2; @@ -121,7 +121,7 @@ var defaults = { t -= 2; return (Math.sqrt(1 - t * t) + 1) / 2; }, - easeInElastic: function(t) { + easeInElastic: function(t: number): number { var s = 1.70158; var p = 0.3; if (t === 0) { @@ -134,7 +134,7 @@ var defaults = { t -= 1; return -(Math.pow(2, 10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); }, - easeOutElastic: function(t) { + easeOutElastic: function(t: number): number { var s = 1.70158; var p = 0.3; if (t === 0) { @@ -146,7 +146,7 @@ var defaults = { var s = p / (2 * Math.PI) * Math.asin(1); return Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; }, - easeInOutElastic: function(t) { + easeInOutElastic: function(t: number): number { var s = 1.70158; var p = 0.3 * 1.5; if (t === 0) { @@ -164,16 +164,16 @@ var defaults = { t -= 1; return Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) / 2 + 1; }, - easeInBack: function(t) { + easeInBack: function(t: number): number { var s = 1.70158; return t * t * ((s + 1) * t - s); }, - easeOutBack: function(t) { + easeOutBack: function(t: number): number { var s = 1.70158; t -= 1; return (t * t * ((s + 1) * t + s) + 1); }, - easeInOutBack: function(t) { + easeInOutBack: function(t: number): number { var s = 1.70158 * 1.525; t *= 2; if (t < 1) { @@ -182,10 +182,10 @@ var defaults = { t -= 2; return (t * t * ((s + 1) * t + s) + 2) / 2; }, - easeInBounce: function(t) { + easeInBounce: function(t: number): number { return 1 - this.easeOutBounce(1 - t); }, - easeOutBounce: function(t) { + easeOutBounce: function(t: number): number { if (t < (1 / 2.75)) { return 7.5625 * t * t; } else if (t < (2 / 2.75)) { @@ -199,7 +199,7 @@ var defaults = { return 7.5625 * t * t + 0.984375; } }, - easeInOutBounce: function(t) { + easeInOutBounce: function(t: number): number { if (t < 0.5) { return this.easeInBounce(t * 2) / 2; } @@ -234,4 +234,6 @@ module.exports = { return samples; }, + + Defaults: defaults, }; diff --git a/Libraries/Animation/LayoutAnimation.js b/Libraries/Animation/LayoutAnimation.js index 12128055c..c297123ba 100644 --- a/Libraries/Animation/LayoutAnimation.js +++ b/Libraries/Animation/LayoutAnimation.js @@ -17,18 +17,20 @@ var RCTUIManager = require('NativeModules').UIManager; var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); var keyMirror = require('keyMirror'); -var Types = keyMirror({ +var TypesEnum = { spring: true, linear: true, easeInEaseOut: true, easeIn: true, easeOut: true, -}); +}; +var Types = keyMirror(TypesEnum); -var Properties = keyMirror({ +var PropertiesEnum = { opacity: true, scaleXY: true, -}); +}; +var Properties = keyMirror(PropertiesEnum); var animChecker = createStrictShapeTypeChecker({ duration: PropTypes.number, @@ -48,8 +50,8 @@ type Anim = { delay?: number; springDamping?: number; initialVelocity?: number; - type?: $Enum; - property?: $Enum; + type?: $Enum; + property?: $Enum; } var configChecker = createStrictShapeTypeChecker({ diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index b7c76c9f5..eb2ddd1cd 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -13,6 +13,7 @@ #import "RCTSparseArray.h" #import "RCTUIManager.h" +#import "RCTUtils.h" #if CGFLOAT_IS_DOUBLE #define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_D @@ -23,14 +24,45 @@ @implementation RCTAnimationExperimentalManager { RCTSparseArray *_animationRegistry; // Main thread only; animation tag -> view tag + RCTSparseArray *_callbackRegistry; // Main thread only; animation tag -> callback + NSDictionary *_keypathMapping; } +RCT_EXPORT_MODULE() + @synthesize bridge = _bridge; - (instancetype)init { if ((self = [super init])) { _animationRegistry = [[RCTSparseArray alloc] init]; + _callbackRegistry = [[RCTSparseArray alloc] init]; + _keypathMapping = @{ + @"opacity": @{ + @"keypath": @"opacity", + @"type": @"NSNumber", + }, + @"position": @{ + @"keypath": @"position", + @"type": @"CGPoint", + }, + @"positionX": @{ + @"keypath": @"position.x", + @"type": @"NSNumber", + }, + @"positionY": @{ + @"keypath": @"position.y", + @"type": @"NSNumber", + }, + @"rotation": @{ + @"keypath": @"transform.rotation.z", + @"type": @"NSNumber", + }, + @"scaleXY": @{ + @"keypath": @"transform.scale", + @"type": @"CGPoint", + }, + }; } return self; @@ -61,10 +93,26 @@ }; } -- (void)startAnimationForTag:(NSNumber *)reactTag animationTag:(NSNumber *)animationTag duration:(double)duration delay:(double)delay easingSample:(NSArray *)easingSample properties:(NSDictionary *)properties +static void RCTInvalidAnimationProp(RCTSparseArray *callbacks, NSNumber *tag, NSString *key, id value) { - RCT_EXPORT(startAnimation); + RCTResponseSenderBlock callback = callbacks[tag]; + RCTLogError(@"Invalid animation property `%@ = %@`", key, value); + if (callback) { + callback(@[@NO]); + callbacks[tag] = nil; + } + [CATransaction commit]; + return; +} +RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag + animationTag:(NSNumber *)animationTag + duration:(NSTimeInterval)duration + delay:(NSTimeInterval)delay + easingSample:(NSArray *)easingSample + properties:(NSDictionary *)properties + callback:(RCTResponseSenderBlock)callback) +{ __weak RCTAnimationExperimentalManager *weakSelf = self; [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTAnimationExperimentalManager *strongSelf = weakSelf; @@ -74,12 +122,21 @@ RCTLogWarn(@"React tag #%@ is not registered with the view registry", reactTag); return; } - - [properties enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + __block BOOL completionBlockSet = NO; + [CATransaction begin]; + for (NSString *prop in properties) { + NSString *keypath = _keypathMapping[prop][@"keypath"]; + id obj = properties[prop][@"to"]; + if (!keypath) { + return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj); + } NSValue *toValue = nil; - if ([key isEqualToString:@"scaleXY"]) { - key = @"transform.scale"; - toValue = obj[0]; + if ([keypath isEqualToString:@"transform.scale"]) { + CGPoint point = [RCTConvert CGPoint:obj]; + if (point.x != point.y) { + return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj); + } + toValue = @(point.x); } else if ([obj respondsToSelector:@selector(count)]) { switch ([obj count]) { case 2: @@ -95,11 +152,15 @@ case 16: toValue = [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:obj]]; break; + default: + return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj); } + } else if (![obj respondsToSelector:@selector(objCType)]) { + return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj); + } + if (!toValue) { + toValue = obj; } - - if (!toValue) toValue = obj; - const char *typeName = toValue.objCType; size_t count; @@ -150,7 +211,7 @@ break; } - NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:key]; + NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:keypath]; CGFloat fromFields[count]; [fromValue getValue:fromFields]; @@ -161,27 +222,38 @@ CGFloat t = sample.CG_APPEND(, floatValue, doubleValue); [sampledValues addObject:interpolationBlock(t)]; } - - CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:key]; - animation.beginTime = CACurrentMediaTime() + delay / 1000.0; - animation.duration = duration / 1000.0; + CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keypath]; + animation.beginTime = CACurrentMediaTime() + delay; + animation.duration = duration; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; animation.values = sampledValues; - - [view.layer setValue:toValue forKey:key]; - - NSString *animationKey = [NSString stringWithFormat:@"RCT.%@.%@", animationTag, key]; - [view.layer addAnimation:animation forKey:animationKey]; - }]; - + @try { + [view.layer setValue:toValue forKey:keypath]; + NSString *animationKey = [@"RCT" stringByAppendingString:RCTJSONStringify(@{@"tag": animationTag, @"key": keypath}, nil)]; + [view.layer addAnimation:animation forKey:animationKey]; + if (!completionBlockSet) { + strongSelf->_callbackRegistry[animationTag] = callback; + [CATransaction setCompletionBlock:^{ + RCTResponseSenderBlock cb = strongSelf->_callbackRegistry[animationTag]; + if (cb) { + cb(@[@YES]); + strongSelf->_callbackRegistry[animationTag] = nil; + } + }]; + completionBlockSet = YES; + } + } + @catch (NSException *exception) { + return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, toValue); + } + } + [CATransaction commit]; strongSelf->_animationRegistry[animationTag] = reactTag; }]; } -- (void)stopAnimation:(NSNumber *)animationTag +RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag) { - RCT_EXPORT(stopAnimation); - __weak RCTAnimationExperimentalManager *weakSelf = self; [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTAnimationExperimentalManager *strongSelf = weakSelf; @@ -191,19 +263,25 @@ UIView *view = viewRegistry[reactTag]; for (NSString *animationKey in view.layer.animationKeys) { - if ([animationKey hasPrefix:@"RCT"]) { - NSRange periodLocation = [animationKey rangeOfString:@"." options:0 range:(NSRange){3, animationKey.length - 3}]; - if (periodLocation.location != NSNotFound) { - NSInteger integerTag = [[animationKey substringWithRange:(NSRange){3, periodLocation.location}] integerValue]; - if (animationTag.integerValue == integerTag) { - [view.layer removeAnimationForKey:animationKey]; - } + if ([animationKey hasPrefix:@"RCT{"]) { + NSDictionary *data = RCTJSONParse([animationKey substringFromIndex:3], nil); + if (animationTag.integerValue == [data[@"tag"] integerValue]) { + [view.layer removeAnimationForKey:animationKey]; } } } - + RCTResponseSenderBlock cb = strongSelf->_callbackRegistry[animationTag]; + if (cb) { + cb(@[@NO]); + strongSelf->_callbackRegistry[animationTag] = nil; + } strongSelf->_animationRegistry[animationTag] = nil; }]; } +- (NSDictionary *)constantsToExport +{ + return @{@"Properties": [_keypathMapping allKeys] }; +} + @end diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index dc29af70a..db6ceea33 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -492,7 +492,7 @@ var TextInput = React.createClass({ _renderAndroid: function() { var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; - return ( + var textContainer = + value={this.state.bufferedValue} + />; + + return ( + + {textContainer} + ); }, diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index ffcc8e737..7cba22164 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -22,7 +22,7 @@ var copyProperties = require('copyProperties'); var onlyChild = require('onlyChild'); type State = { - animationID: ?number; + animationID: ?number; }; /** @@ -60,7 +60,7 @@ var TouchableBounce = React.createClass({ value: number, velocity: number, bounciness: number, - fromValue?: ?Function | number, + fromValue?: ?number, callback?: ?Function ) { if (POPAnimation) { @@ -71,21 +71,21 @@ var TouchableBounce = React.createClass({ toValue: [value, value], velocity: [velocity, velocity], springBounciness: bounciness, - fromValue: (undefined: ?any), + fromValue: fromValue ? [fromValue, fromValue] : undefined, }; - if (fromValue) { - anim.fromValue = [fromValue, fromValue]; - } this.state.animationID = POPAnimation.createSpringAnimation(anim); this.addAnimation(this.state.animationID, callback); } else { - AnimationExperimental.startAnimation(this, 300, 0, 'easeOutBack', {scaleXY: [value, value]}); - if (fromValue && typeof fromValue === 'function') { - callback = fromValue; - } - if (callback) { - setTimeout(callback, 300); - } + AnimationExperimental.startAnimation( + { + node: this, + duration: 300, + easing: 'easeOutBack', + property: 'scaleXY', + toValue: { x: value, y: value}, + }, + callback + ); } }, diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 0ad3c4269..f7af45ffe 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -28,7 +28,6 @@ var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule; var BackAndroid = require('BackAndroid'); -var Dimensions = require('Dimensions'); var InteractionMixin = require('InteractionMixin'); var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar'); var NavigatorInterceptor = require('NavigatorInterceptor'); @@ -52,9 +51,6 @@ var rebound = require('rebound'); var PropTypes = React.PropTypes; -var SCREEN_WIDTH = Dimensions.get('window').width; -var SCREEN_HEIGHT = Dimensions.get('window').height; - var OFF_SCREEN = {style: {opacity: 0}}; var __uid = 0; @@ -69,8 +65,11 @@ var styles = StyleSheet.create({ overflow: 'hidden', }, defaultSceneStyle: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, }, presentNavItem: { position: 'absolute', @@ -88,7 +87,7 @@ var styles = StyleSheet.create({ }, transitioner: { flex: 1, - backgroundColor: '#555555', + backgroundColor: 'transparent', overflow: 'hidden', } }); @@ -510,7 +509,7 @@ var Navigator = React.createClass({ this.state.fromIndex = this.state.presentedIndex; this.state.toIndex = this.state.presentedIndex; } - this._hideOtherScenes(presentedIndex); + this._hideOtherScenes(this.state.presentedIndex); }, _transitionToToIndexWithVelocity: function(v) { @@ -596,7 +595,7 @@ var Navigator = React.createClass({ _handlePanResponderGrant: function(e, gestureState) { invariant( - this._expectingGestureGrant, + this._expectingGestureGrant || this.state.isAnimating, 'Responder granted unexpectedly.' ); this._activeGestureAction = this._expectingGestureGrant; diff --git a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js index 3072074b2..ac16542ad 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js +++ b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js @@ -34,7 +34,7 @@ var buildStyleInterpolator = require('buildStyleInterpolator'); var SCREEN_WIDTH = Dimensions.get('window').width; var SCREEN_HEIGHT = Dimensions.get('window').height; -var ToTheLeft = { +var FadeToTheLeft = { // Rotate *requires* you to break out each individual component of // rotation (x, y, z, w) transformTranslate: { @@ -101,6 +101,23 @@ var ToTheLeft = { }, }; +var ToTheLeft = { + transformTranslate: { + from: {x: 0, y: 0, z: 0}, + to: {x: -Dimensions.get('window').width, y: 0, z: 0}, + min: 0, + max: 1, + type: 'linear', + extrapolate: true, + round: PixelRatio.get(), + }, + opacity: { + value: 1.0, + type: 'constant', + }, +}; + + var FromTheRight = { opacity: { value: 1.0, @@ -271,7 +288,7 @@ var BaseConfig = { // Animation interpolators for horizontal transitioning: animationInterpolators: { into: buildStyleInterpolator(FromTheRight), - out: buildStyleInterpolator(ToTheLeft), + out: buildStyleInterpolator(FadeToTheLeft), }, }; @@ -312,8 +329,12 @@ var NavigatorSceneConfigs = { overswipe: BaseOverswipeConfig, edgeHitWidth: null, }, - } - } + }, + animationInterpolators: { + into: buildStyleInterpolator(FromTheRight), + out: buildStyleInterpolator(ToTheLeft), + }, + }, }; module.exports = NavigatorSceneConfigs; diff --git a/Libraries/Geolocation/Geolocation.ios.js b/Libraries/Geolocation/Geolocation.ios.js index 6a022f76f..13fe40a23 100644 --- a/Libraries/Geolocation/Geolocation.ios.js +++ b/Libraries/Geolocation/Geolocation.ios.js @@ -23,11 +23,9 @@ var subscriptions = []; var updatesEnabled = false; /** - * /!\ ATTENTION /!\ - * You need to add NSLocationWhenInUseUsageDescription key - * in Info.plist to enable geolocation, otherwise it's going - * to *fail silently*! - * \!/ \!/ + * You need to include the `NSLocationWhenInUseUsageDescription` key + * in Info.plist to enable geolocation. Geolocation is enabled by default + * when you create a project with `react-native init`. * * Geolocation follows the MDN specification: * https://developer.mozilla.org/en-US/docs/Web/API/Geolocation diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index d1182bbe6..6bb95acb0 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -99,6 +99,8 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg / RCTLocationOptions _observerOptions; } +RCT_EXPORT_MODULE() + @synthesize bridge = _bridge; #pragma mark - Lifecycle @@ -151,9 +153,9 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg / #pragma mark - Public API -- (void)startObserving:(NSDictionary *)optionsJSON +RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON) { - RCT_EXPORT(); + [self checkLocationConfig]; dispatch_async(dispatch_get_main_queue(), ^{ @@ -170,10 +172,8 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg / }); } -- (void)stopObserving +RCT_EXPORT_METHOD(stopObserving) { - RCT_EXPORT(); - dispatch_async(dispatch_get_main_queue(), ^{ // Stop observing @@ -187,11 +187,11 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg / }); } -- (void)getCurrentPosition:(NSDictionary *)optionsJSON - withSuccessCallback:(RCTResponseSenderBlock)successBlock - errorCallback:(RCTResponseSenderBlock)errorBlock +RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON + withSuccessCallback:(RCTResponseSenderBlock)successBlock + errorCallback:(RCTResponseSenderBlock)errorBlock) { - RCT_EXPORT(); + [self checkLocationConfig]; if (!successBlock) { RCTLogError(@"%@.getCurrentPosition called with nil success parameter.", [self class]); @@ -323,4 +323,11 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg / _locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY; } +- (void)checkLocationConfig +{ + if (![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) { + RCTLogError(@"NSLocationWhenInUseUsageDescription key must be present in Info.plist to use geolocation."); + } +} + @end diff --git a/Libraries/Image/RCTCameraRollManager.m b/Libraries/Image/RCTCameraRollManager.m index b0734b1bd..8e6c8a532 100644 --- a/Libraries/Image/RCTCameraRollManager.m +++ b/Libraries/Image/RCTCameraRollManager.m @@ -14,15 +14,17 @@ #import #import - #import "RCTImageLoader.h" +#import "RCTImageLoader.h" #import "RCTLog.h" @implementation RCTCameraRollManager -- (void)saveImageWithTag:(NSString *)imageTag successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseSenderBlock)errorCallback -{ - RCT_EXPORT(); +RCT_EXPORT_MODULE() +RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag + successCallback:(RCTResponseSenderBlock)successCallback + errorCallback:(RCTResponseSenderBlock)errorCallback) +{ [RCTImageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) { if (loadError) { errorCallback(@[[loadError localizedDescription]]); @@ -59,10 +61,10 @@ }]); } -- (void)getPhotos:(NSDictionary *)params callback:(RCTResponseSenderBlock)callback errorCallback:(RCTResponseSenderBlock)errorCallback +RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params + callback:(RCTResponseSenderBlock)callback + errorCallback:(RCTResponseSenderBlock)errorCallback) { - RCT_EXPORT(); - NSUInteger first = [params[@"first"] integerValue]; NSString *afterCursor = params[@"after"]; NSString *groupTypesStr = params[@"groupTypes"]; diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m index 22268e8c0..005b726cf 100644 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ b/Libraries/Image/RCTNetworkImageViewManager.m @@ -18,6 +18,8 @@ @implementation RCTNetworkImageViewManager +RCT_EXPORT_MODULE() + - (UIView *)view { RCTNetworkImageView *view = [[RCTNetworkImageView alloc] initWithFrame:CGRectZero imageDownloader:[RCTImageDownloader sharedInstance]]; diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m index 7668613bd..2d80117e4 100644 --- a/Libraries/Image/RCTStaticImageManager.m +++ b/Libraries/Image/RCTStaticImageManager.m @@ -18,6 +18,8 @@ @implementation RCTStaticImageManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTStaticImage alloc] init]; diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 565469c5d..33fac75f0 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -18,6 +18,8 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification"; @synthesize bridge = _bridge; +RCT_EXPORT_MODULE() + - (instancetype)init { if ((self = [super init])) { @@ -52,19 +54,15 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification"; body:[notification userInfo]]; } -- (void)openURL:(NSString *)url +RCT_EXPORT_METHOD(openURL:(NSURL *)url) { - RCT_EXPORT(); - - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; + [[UIApplication sharedApplication] openURL:url]; } -- (void)canOpenURL:(NSString *)url - callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(canOpenURL:(NSURL *)url + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - - BOOL supported = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]; + BOOL supported = [[UIApplication sharedApplication] canOpenURL:url]; callback(@[@(supported)]); } diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTDataManager.m index 13e3c37ac..6aa2842a3 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTDataManager.m @@ -15,17 +15,17 @@ @implementation RCTDataManager +RCT_EXPORT_MODULE() + /** * Executes a network request. * The responseSender block won't be called on same thread as called. */ -- (void)executeQuery:(NSString *)queryType - query:(id)query - queryHash:(__unused NSString *)queryHash - responseSender:(RCTResponseSenderBlock)responseSender +RCT_EXPORT_METHOD(queryData:(NSString *)queryType + withQuery:(id)query + queryHash:(__unused NSString *)queryHash + responseSender:(RCTResponseSenderBlock)responseSender) { - RCT_EXPORT(queryData); - if ([queryType isEqualToString:@"http"]) { // Parse query diff --git a/Libraries/Network/RCTReachability.m b/Libraries/Network/RCTReachability.m index 2dec4f812..b5f30de30 100644 --- a/Libraries/Network/RCTReachability.m +++ b/Libraries/Network/RCTReachability.m @@ -53,6 +53,8 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC } } +RCT_EXPORT_MODULE() + #pragma mark - Lifecycle - (instancetype)initWithHost:(NSString *)host @@ -81,11 +83,9 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC #pragma mark - Public API // TODO: remove error callback - not needed except by Subscribable interface -- (void)getCurrentReachability:(RCTResponseSenderBlock)getSuccess - withErrorCallback:(__unused RCTResponseSenderBlock)getError +RCT_EXPORT_METHOD(getCurrentReachability:(RCTResponseSenderBlock)getSuccess + withErrorCallback:(__unused RCTResponseSenderBlock)getError) { - RCT_EXPORT(); - getSuccess(@[@{@"network_reachability": _status}]); } diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 6a54e457c..17ceb204c 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -19,6 +19,8 @@ NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; NSDictionary *_initialNotification; } +RCT_EXPORT_MODULE() + @synthesize bridge = _bridge; - (instancetype)init @@ -66,29 +68,23 @@ NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; /** * Update the application icon badge number on the home screen */ -+ (void)setApplicationIconBadgeNumber:(NSInteger)number +RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(NSInteger)number) { - RCT_EXPORT(); - [UIApplication sharedApplication].applicationIconBadgeNumber = number; } /** * Get the current application icon badge number on the home screen */ -+ (void)getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - callback(@[ @([UIApplication sharedApplication].applicationIconBadgeNumber) ]); } -+ (void)requestPermissions +RCT_EXPORT_METHOD(requestPermissions) { - RCT_EXPORT(); - #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 // if we are targeting iOS 7, *and* the new UIUserNotificationSettings @@ -104,13 +100,10 @@ NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert; UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; - } -+ (void)checkPermissions:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - NSMutableDictionary *permissions = [[NSMutableDictionary alloc] init]; UIUserNotificationType types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types]; diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 88bbe0ee6..58b6572f8 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -13,12 +13,15 @@ #import "RCTAssert.h" #import "RCTLog.h" -@implementation RCTTestModule { +@implementation RCTTestModule +{ __weak FBSnapshotTestController *_snapshotController; __weak UIView *_view; NSMutableDictionary *_snapshotCounter; } +RCT_EXPORT_MODULE() + - (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view { if ((self = [super init])) { @@ -29,10 +32,8 @@ return self; } -- (void)verifySnapshot:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - if (!_snapshotController) { RCTLogWarn(@"No snapshot controller configured."); callback(@[]); @@ -52,10 +53,8 @@ }); } -- (void)markTestCompleted +RCT_EXPORT_METHOD(markTestCompleted) { - RCT_EXPORT(); - dispatch_async(dispatch_get_main_queue(), ^{ _done = YES; }); diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index 4d6aba5cc..2f74628c0 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -47,7 +47,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; if (![self connectToProxy]) { - RCTLogError(@"Connection to %@ timed out. Are you running node proxy?", url); + RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If you are running on the device check if you have the right IP address on `RCTWebSocketExecutor.m` file.", url); [self invalidate]; return nil; } diff --git a/Libraries/RCTWebSocketDebugger/SRWebSocket.m b/Libraries/RCTWebSocketDebugger/SRWebSocket.m index 8cf5b4d74..3fd675103 100644 --- a/Libraries/RCTWebSocketDebugger/SRWebSocket.m +++ b/Libraries/RCTWebSocketDebugger/SRWebSocket.m @@ -19,9 +19,12 @@ #import -#if TARGET_OS_IPHONE -#define HAS_ICU -#endif +//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. + +//#if TARGET_OS_IPHONE +//#define HAS_ICU +//#endif #ifdef HAS_ICU #import diff --git a/Libraries/Text/RCTRawTextManager.m b/Libraries/Text/RCTRawTextManager.m index ab856d049..221b8daeb 100644 --- a/Libraries/Text/RCTRawTextManager.m +++ b/Libraries/Text/RCTRawTextManager.m @@ -13,6 +13,8 @@ @implementation RCTRawTextManager +RCT_EXPORT_MODULE() + - (UIView *)view { return nil; diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index d6e00f1b6..87c625cd3 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -38,34 +38,53 @@ - (void)setAttributedText:(NSAttributedString *)attributedText { - [_textStorage setAttributedString:attributedText]; + for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) { + [_textStorage removeLayoutManager:existingLayoutManager]; + } + + _textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText]; + + if (_layoutManager) { + [_textStorage addLayoutManager:_layoutManager]; + } + [self setNeedsDisplay]; } - (void)setTextContainer:(NSTextContainer *)textContainer { - if ([_textContainer isEqual:textContainer]) return; + if ([_textContainer isEqual:textContainer]) { + return; + } _textContainer = textContainer; for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) { [_layoutManager removeTextContainerAtIndex:i]; } - [_layoutManager addTextContainer:_textContainer]; + + if (_textContainer) { + [_layoutManager addTextContainer:_textContainer]; + } [self setNeedsDisplay]; } - (void)setLayoutManager:(NSLayoutManager *)layoutManager { - if ([_layoutManager isEqual:layoutManager]) return; + if ([_layoutManager isEqual:layoutManager]) { + return; + } _layoutManager = layoutManager; for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) { [_textStorage removeLayoutManager:existingLayoutManager]; } - [_textStorage addLayoutManager:_layoutManager]; + + if (_layoutManager) { + [_textStorage addLayoutManager:_layoutManager]; + } [self setNeedsDisplay]; } diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index d19dfa71e..b8dabcf2c 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -20,6 +20,8 @@ @implementation RCTTextManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTText alloc] init]; diff --git a/Libraries/Vibration/RCTVibration.m b/Libraries/Vibration/RCTVibration.m index 0680860b1..f2544d767 100644 --- a/Libraries/Vibration/RCTVibration.m +++ b/Libraries/Vibration/RCTVibration.m @@ -13,9 +13,10 @@ @implementation RCTVibration -- (void)vibrate +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD(vibrate) { - RCT_EXPORT(); AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); } diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 3f0ad735e..f5c21bb3c 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -26,8 +26,6 @@ */ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void); -extern NSString *const RCTReloadBridge; - /** * This function returns the module name for a given class. */ @@ -38,8 +36,6 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @interface RCTBridge : NSObject -@property (nonatomic, assign, readonly, getter=isLoaded) BOOL loaded; - /** * The designated initializer. This creates a new bridge on top of the specified * executor. The bridge should then be used for all subsequent communication @@ -55,16 +51,31 @@ 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. + * with the JavaScript code. Method should be regsitered using the + * RCT_IMPORT_METHOD macro below. Attempting to call a method that has not been + * registered will result in an error. */ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args; +/** + * This macro is used to register a JS method to be called via the enqueueJSCall + * bridge method. You should place this macro inside any file that uses the + * imported method. If a method has already been registered by another class, it + * is not necessary to register it again, but it is good practice. Registering + * the same method more than once will not result in an error. + */ +#define RCT_IMPORT_METHOD(module, method) \ +__attribute__((used, section("__DATA,RCTImport"))) \ +static const char *__rct_import_##module##_##method##__ = #module"."#method; + /** * This method is used to execute a new application script. It is called * internally whenever a JS application bundle is loaded/reloaded, but should * probably not be used at any other time. */ -- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete; +- (void)enqueueApplicationScript:(NSString *)script + url:(NSURL *)url + onComplete:(RCTJavaScriptCompleteBlock)onComplete; @property (nonatomic, strong) Class executorClass; @@ -86,14 +97,19 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, readonly) dispatch_queue_t shadowQueue; +/** + * The launch options that were used to initialize the bridge. + */ @property (nonatomic, copy, readonly) NSDictionary *launchOptions; +/** + * Use this to check if the bridge is currently loading. + */ +@property (nonatomic, readonly, getter=isLoaded) BOOL loaded; /** - * Method to check that a valid executor exists with which to log + * Reload the bundle and reset executor and modules. */ -+ (BOOL)hasValidJSExecutor; - - (void)reload; @end diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 3bf23a5d3..c48afa565 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -26,7 +26,6 @@ #import "RCTRootView.h" #import "RCTSparseArray.h" #import "RCTUtils.h" -#import "RCTWebViewExecutor.h" /** * Must be kept in sync with `MessageQueue.js`. @@ -40,11 +39,32 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldFlushDateMillis }; -NSString *const RCTReloadBridge = @"RCTReloadBridge"; +#ifdef __LP64__ +typedef uint64_t RCTHeaderValue; +typedef struct section_64 RCTHeaderSection; +#define RCTGetSectByNameFromHeader getsectbynamefromheader_64 +#else +typedef uint32_t RCTHeaderValue; +typedef struct section RCTHeaderSection; +#define RCTGetSectByNameFromHeader getsectbynamefromheader +#endif +/** + * This function returns the module name for a given class. + */ NSString *RCTBridgeModuleNameForClass(Class cls) { - return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls); + NSString *name = nil; + if ([cls respondsToSelector:@selector(moduleName)]) { + name = [cls valueForKey:@"moduleName"]; + } + if ([name length] == 0) { + name = NSStringFromClass(cls); + } + if ([name hasPrefix:@"RK"]) { + name = [name stringByReplacingCharactersInRange:(NSRange){0,@"RK".length} withString:@"RCT"]; + } + return name; } /** @@ -58,11 +78,22 @@ static NSArray *RCTJSMethods(void) dispatch_once(&onceToken, ^{ NSMutableSet *uniqueMethods = [NSMutableSet set]; - RCTEnumerateClasses(^(__unsafe_unretained Class cls) { - if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) { - [uniqueMethods addObjectsFromArray:[cls JSMethods]]; + Dl_info info; + dladdr(&RCTJSMethods, &info); + + const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; + const RCTHeaderSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTImport"); + + if (section) { + for (RCTHeaderValue addr = section->offset; + addr < section->offset + section->size; + addr += sizeof(const char **)) { + + // Get data entry + NSString *entry = @(*(const char **)(mach_header + addr)); + [uniqueMethods addObject:entry]; } - }); + } JSMethods = [uniqueMethods allObjects]; }); @@ -71,35 +102,85 @@ static NSArray *RCTJSMethods(void) } /** - * This function scans all classes available at runtime and returns an array - * of all classes that implement the RTCBridgeModule protocol. + * This function scans all exported modules available at runtime and returns an + * array. As a backup, it also scans all classes that implement the + * RTCBridgeModule protocol to ensure they've been exported. This scanning + * functionality is disabled in release mode to improve startup performance. */ static NSArray *RCTModuleNamesByID; +static NSArray *RCTModuleClassesByID; static NSArray *RCTBridgeModuleClassesByModuleID(void) { - static NSArray *modules; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - modules = [NSMutableArray array]; + RCTModuleNamesByID = [NSMutableArray array]; + RCTModuleClassesByID = [NSMutableArray array]; - RCTEnumerateClasses(^(__unsafe_unretained Class cls) { - if ([cls conformsToProtocol:@protocol(RCTBridgeModule)]) { + Dl_info info; + dladdr(&RCTBridgeModuleClassesByModuleID, &info); - // Add module - [(NSMutableArray *)modules addObject:cls]; + const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; + const RCTHeaderSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExportModule"); - // Add module name - NSString *moduleName = RCTBridgeModuleNameForClass(cls); - [(NSMutableArray *)RCTModuleNamesByID addObject:moduleName]; + if (section) { + for (RCTHeaderValue addr = section->offset; + addr < section->offset + section->size; + addr += sizeof(const char **)) { + + // Get data entry + NSString *entry = @(*(const char **)(mach_header + addr)); + NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}] componentsSeparatedByString:@" "]; + + // Parse class name + NSString *moduleClassName = parts[0]; + NSRange categoryRange = [moduleClassName rangeOfString:@"("]; + if (categoryRange.length) { + moduleClassName = [moduleClassName substringToIndex:categoryRange.location]; + } + + // Get class + Class cls = NSClassFromString(moduleClassName); + RCTCAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], + @"%@ does not conform to the RCTBridgeModule protocol", + NSStringFromClass(cls)); + + // Register module + [(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)]; + [(NSMutableArray *)RCTModuleClassesByID addObject:cls]; } - }); + } + +#if 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 + + 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) + { + 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; + } + superclass = class_getSuperclass(superclass); + } + } + +#endif - modules = [modules copy]; - RCTModuleNamesByID = [RCTModuleNamesByID copy]; }); - return modules; + return RCTModuleClassesByID; } @interface RCTBridge () @@ -132,30 +213,61 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) static Class _globalExecutorClass; +NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) { + NSRange colonRange = [methodName rangeOfString:@":"]; + if (colonRange.length) { + methodName = [methodName substringToIndex:colonRange.location]; + } + return methodName; +} + - (instancetype)initWithMethodName:(NSString *)methodName JSMethodName:(NSString *)JSMethodName { if ((self = [super init])) { - _methodName = methodName; - NSArray *parts = [[methodName substringWithRange:NSMakeRange(2, methodName.length - 3)] componentsSeparatedByString:@" "]; + NSArray *parts = [[methodName substringWithRange:(NSRange){2, methodName.length - 3}] componentsSeparatedByString:@" "]; // Parse class and method _moduleClassName = parts[0]; NSRange categoryRange = [_moduleClassName rangeOfString:@"("]; - if (categoryRange.length) - { + if (categoryRange.length) { _moduleClassName = [_moduleClassName substringToIndex:categoryRange.location]; } + NSArray *argumentNames = nil; + if ([parts[1] hasPrefix:@"__rct_export__"]) { + // New format + NSString *selectorString = [parts[1] substringFromIndex:14]; + _selector = NSSelectorFromString(selectorString); + _JSMethodName = RCTStringUpToFirstArgument(selectorString); + + static NSRegularExpression *regExp; + if (!regExp) { + NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))"; + NSString *constPattern = @"(?:const)"; + NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern]; + NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern]; + regExp = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL]; + } + + argumentNames = [NSMutableArray array]; + [regExp enumerateMatchesInString:JSMethodName options:0 range:NSMakeRange(0, JSMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + NSString *argumentName = [JSMethodName substringWithRange:[result rangeAtIndex:1]]; + [(NSMutableArray *)argumentNames addObject:argumentName]; + }]; + } else { + // Old format + NSString *selectorString = parts[1]; + _selector = NSSelectorFromString(selectorString); + _JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString); + } + // Extract class and method details _isClassMethod = [methodName characterAtIndex:0] == '+'; _moduleClass = NSClassFromString(_moduleClassName); - _selector = NSSelectorFromString(parts[1]); - _JSMethodName = JSMethodName ?: [NSStringFromSelector(_selector) componentsSeparatedByString:@":"][0]; #if DEBUG - // Sanity check RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], @"You are attempting to export the method %@, but %@ does not \ @@ -170,15 +282,87 @@ static Class _globalExecutorClass; // Process arguments NSUInteger numberOfArguments = _methodSignature.numberOfArguments; NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2]; - for (NSUInteger i = 2; i < numberOfArguments; i++) { - const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i]; - switch (argumentType[0]) { #define RCT_ARG_BLOCK(_logic) \ - [argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \ - _logic \ - [invocation setArgument:&value atIndex:index]; \ - }]; \ + [argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \ + _logic \ + [invocation setArgument:&value atIndex:index]; \ + }]; \ + + void (^addBlockArgument)(void) = ^{ + RCT_ARG_BLOCK( + if (json && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, + json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); + return; + } + + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing id value = (json ? ^(NSArray *args) { + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, args]]; + } : ^(NSArray *unused) {}); + ) + }; + + void (^defaultCase)(const char *) = ^(const char *argumentType) { + static const char *blockType = @encode(typeof(^{})); + if (!strcmp(argumentType, blockType)) { + addBlockArgument(); + } else { + RCT_ARG_BLOCK( id value = json; ) + } + }; + + for (NSUInteger i = 2; i < numberOfArguments; i++) { + const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i]; + + BOOL useFallback = YES; + if (argumentNames) { + NSString *argumentName = argumentNames[i - 2]; + SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]); + if ([RCTConvert respondsToSelector:selector]) { + useFallback = NO; + switch (argumentType[0]) { + +#define RCT_CONVERT_CASE(_value, _type) \ + case _value: { \ + _type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \ + RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \ + break; \ + } + + RCT_CONVERT_CASE(':', SEL) + RCT_CONVERT_CASE('*', const char *) + RCT_CONVERT_CASE('c', char) + RCT_CONVERT_CASE('C', unsigned char) + RCT_CONVERT_CASE('s', short) + RCT_CONVERT_CASE('S', unsigned short) + RCT_CONVERT_CASE('i', int) + RCT_CONVERT_CASE('I', unsigned int) + RCT_CONVERT_CASE('l', long) + RCT_CONVERT_CASE('L', unsigned long) + RCT_CONVERT_CASE('q', long long) + RCT_CONVERT_CASE('Q', unsigned long long) + RCT_CONVERT_CASE('f', float) + RCT_CONVERT_CASE('d', double) + RCT_CONVERT_CASE('B', BOOL) + RCT_CONVERT_CASE('@', id) + RCT_CONVERT_CASE('^', void *) + + default: + defaultCase(argumentType); + break; + } + } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { + addBlockArgument(); + useFallback = NO; + } + } + + if (useFallback) { + switch (argumentType[0]) { #define RCT_CASE(_value, _class, _logic) \ case _value: { \ @@ -193,61 +377,46 @@ static Class _globalExecutorClass; break; \ } - RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); ); - RCT_CASE('*', NSString, const char *value = [json UTF8String]; ); + RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); ) + RCT_CASE('*', NSString, const char *value = [json UTF8String]; ) #define RCT_SIMPLE_CASE(_value, _type, _selector) \ - case _value: { \ - RCT_ARG_BLOCK( \ - if (json && ![json respondsToSelector:@selector(_selector)]) { \ - RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \ - index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \ - return; \ - } \ - _type value = [json _selector]; \ - ) \ - break; \ - } - - RCT_SIMPLE_CASE('c', char, charValue) - RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue) - RCT_SIMPLE_CASE('s', short, shortValue) - RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue) - RCT_SIMPLE_CASE('i', int, intValue) - RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue) - RCT_SIMPLE_CASE('l', long, longValue) - RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue) - RCT_SIMPLE_CASE('q', long long, longLongValue) - RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue) - RCT_SIMPLE_CASE('f', float, floatValue) - RCT_SIMPLE_CASE('d', double, doubleValue) - RCT_SIMPLE_CASE('B', BOOL, boolValue) - - default: { - static const char *blockType = @encode(typeof(^{})); - if (!strcmp(argumentType, blockType)) { - RCT_ARG_BLOCK( - if (json && ![json isKindOfClass:[NSNumber class]]) { - RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, - json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); - return; - } - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing id value = (json ? ^(NSArray *args) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, args]]; - } : ^(NSArray *unused) {}); - ) - } else { - RCT_ARG_BLOCK( id value = json; ) + case _value: { \ + RCT_ARG_BLOCK( \ + if (json && ![json respondsToSelector:@selector(_selector)]) { \ + RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \ + index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \ + return; \ + } \ + _type value = [json _selector]; \ + ) \ + break; \ } - break; + + RCT_SIMPLE_CASE('c', char, charValue) + RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue) + RCT_SIMPLE_CASE('s', short, shortValue) + RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue) + RCT_SIMPLE_CASE('i', int, intValue) + RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue) + RCT_SIMPLE_CASE('l', long, longValue) + RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue) + RCT_SIMPLE_CASE('q', long long, longLongValue) + RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue) + RCT_SIMPLE_CASE('f', float, floatValue) + RCT_SIMPLE_CASE('d', double, doubleValue) + RCT_SIMPLE_CASE('B', BOOL, boolValue) + + default: + defaultCase(argumentType); + break; } } } + _argumentBlocks = [argumentBlocks copy]; } + return self; } @@ -257,7 +426,6 @@ static Class _globalExecutorClass; { #if DEBUG - // Sanity check RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ %@ on a module of class %@", _methodName, [module class]); @@ -274,6 +442,7 @@ static Class _globalExecutorClass; // Create invocation (we can't re-use this as it wouldn't be thread-safe) NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature]; [invocation setArgument:&_selector atIndex:1]; + [invocation retainArguments]; // Set arguments NSUInteger index = 0; @@ -281,7 +450,7 @@ static Class _globalExecutorClass; id arg = (json == [NSNull null]) ? nil : json; void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index]; block(bridge, invocation, index + 2, arg); - index ++; + index++; } // Invoke method @@ -309,18 +478,8 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) Dl_info info; dladdr(&RCTExportedMethodsByModuleID, &info); -#ifdef __LP64__ - typedef uint64_t RCTExportValue; - typedef struct section_64 RCTExportSection; -#define RCTGetSectByNameFromHeader getsectbynamefromheader_64 -#else - typedef uint32_t RCTExportValue; - typedef struct section RCTExportSection; -#define RCTGetSectByNameFromHeader getsectbynamefromheader -#endif - - const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase; - const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport"); + const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; + const RCTHeaderSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport"); if (section == NULL) { return; @@ -329,7 +488,7 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) NSArray *classes = RCTBridgeModuleClassesByModuleID(); NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]]; - for (RCTExportValue addr = section->offset; + for (RCTHeaderValue addr = section->offset; addr < section->offset + section->size; addr += sizeof(const char **) * 2) { @@ -530,11 +689,7 @@ static id _latestJSExecutor; - (void)setUp { Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class]; - if ([NSStringFromClass(executorClass) isEqualToString:@"RCTWebViewExecutor"]) { - _javaScriptExecutor = [[RCTWebViewExecutor alloc] initWithWebView:[[UIWebView alloc] init]]; - } else { - _javaScriptExecutor = [[executorClass alloc] init]; - } + _javaScriptExecutor = [[executorClass alloc] init]; _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL); @@ -598,32 +753,34 @@ static id _latestJSExecutor; _loaded = YES; } else if (_bundlePath != nil) { // Allow testing without a script RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; - [loader loadBundleAtURL:[NSURL URLWithString:_bundlePath] - onComplete:^(NSError *error) { - _loaded = YES; - if (error != nil) { - NSArray *stack = [[error userInfo] objectForKey:@"stack"]; - if (stack) { - [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack]; - } else { - [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]]; - } - } else { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:self]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reload) - name:RCTReloadNotification - object:nil]; - ; - } - }]; + [loader loadBundleAtURL:[NSURL URLWithString:_bundlePath] onComplete:^(NSError *error) { + _loaded = YES; + if (error != nil) { + NSArray *stack = [[error userInfo] objectForKey:@"stack"]; + if (stack) { + [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] + withStack:stack]; + } else { + [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] + withDetails:[error localizedFailureReason]]; + } + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification + object:self]; + } + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reload) + name:RCTReloadNotification + object:nil]; + }]; } } - (void)bindKeys { #if TARGET_IPHONE_SIMULATOR + __weak RCTBridge *weakSelf = self; + // Workaround around the first cmd+r not working: http://openradar.appspot.com/19613391 // You can register just the cmd key and do nothing. This will trigger the bug and cmd+r // will work like a charm! @@ -632,31 +789,38 @@ static id _latestJSExecutor; action:^(UIKeyCommand *command) { // Do nothing }]; - [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) { - [self reload]; + [weakSelf reload]; }]; [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) { - _executorClass = Nil; - [self reload]; + RCTBridge *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_executorClass = Nil; + [strongSelf reload]; }]; - [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) { - _executorClass = NSClassFromString(@"RCTWebSocketExecutor"); - if (!_executorClass) { + RCTBridge *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_executorClass = NSClassFromString(@"RCTWebSocketExecutor"); + if (!strongSelf->_executorClass) { RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?"); } - [self reload]; + [strongSelf reload]; }]; #endif } + - (NSDictionary *)modules { RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. \ @@ -693,6 +857,11 @@ static id _latestJSExecutor; [_javaScriptExecutor invalidate]; _javaScriptExecutor = nil; + // Wait for queued methods to finish + dispatch_sync(self.shadowQueue, ^{ + // Make sure all dispatchers have been executed before continuing + }); + // Invalidate modules for (id target in _modulesByID.allObjects) { if ([target respondsToSelector:@selector(invalidate)]) { @@ -888,15 +1057,9 @@ static id _latestJSExecutor; } } -+ (BOOL)hasValidJSExecutor -{ - return (_latestJSExecutor != nil && [_latestJSExecutor isValid]); -} - + (void)logMessage:(NSString *)message level:(NSString *)level { if (!_latestJSExecutor || ![_latestJSExecutor isValid]) { - RCTLogError(@"ERROR: No valid JS executor to log '%@'.", message); return; } diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 2134dc2e5..7fc526e35 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -9,8 +9,6 @@ #import -#import "RCTJSMethodRegistrar.h" - @class RCTBridge; /** @@ -22,7 +20,7 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); /** * Provides the interface needed to register a bridge module. */ -@protocol RCTBridgeModule +@protocol RCTBridgeModule @optional /** @@ -34,10 +32,14 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); @property (nonatomic, strong) RCTBridge *bridge; /** - * The module name exposed to JS. If omitted, this will be inferred - * automatically by using the native module's class name. + * Place this macro in your class implementation, to automatically register + * your module with the bridge when it loads. The optional js_name argument + * will be used as the JS module name. If omitted, the JS module name will + * match the Objective-C class name. */ -+ (NSString *)moduleName; +#define RCT_EXPORT_MODULE(js_name) \ ++ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \ +))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; } \ /** * Place this macro inside the method body of any method you want to expose @@ -46,8 +48,37 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); * If omitted, the JS method name will match the first part of the Objective-C * method selector name (up to the first colon). */ -#define RCT_EXPORT(js_name) __attribute__((used, section("__DATA,RCTExport" \ -))) static const char *__rct_export_entry__[] = { __func__, #js_name } +#define RCT_EXPORT(js_name) \ + _Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \ + __attribute__((used, section("__DATA,RCTExport"))) \ + static const char *__rct_export_entry__[] = { __func__, #js_name } + +/** + * Wrap the parameter line of your method implementation with this macro to + * expose it to JS. Unlike the deprecated RCT_EXPORT, this macro does not take + * a js_name argument and the exposed method will match the first part of the + * Objective-C method selector name (up to the first colon). + * + * For example, in MyClass.m: + * + * - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b + * {} + * + * becomes + * + * RCT_EXPORT_METHOD(doSomething:(NSString *)aString + * withA:(NSInteger)a + * andB:(NSInteger)b) + * {} + * + * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`. + */ +#define RCT_EXPORT_METHOD(method) \ + - (void)__rct_export__##method { \ + __attribute__((used, section("__DATA,RCTExport"))) \ + static const char *__rct_export_entry__[] = { __func__, #method }; \ + } \ + - (void)method /** * Injects constants into JS. These constants are made accessible via @@ -65,3 +96,11 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); - (void)batchDidComplete; @end + +#ifdef __cplusplus +extern "C" { +#endif +void RCTBridgeModuleRegisterClass(Class cls, NSString *moduleName); +#ifdef __cplusplus +} +#endif diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 7e573370e..ff5fb970b 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -82,14 +82,26 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight style:(id)style; -+ (NSArray *)NSStringArray:(id)json; -+ (NSArray *)NSDictionaryArray:(id)json; -+ (NSArray *)NSURLArray:(id)json; -+ (NSArray *)NSNumberArray:(id)json; -+ (NSArray *)UIColorArray:(id)json; -+ (NSArray *)CGColorArray:(id)json; +typedef NSArray NSStringArray; ++ (NSStringArray *)NSStringArray:(id)json; -+ (BOOL)css_overflow:(id)json; +typedef NSArray NSDictionaryArray; ++ (NSDictionaryArray *)NSDictionaryArray:(id)json; + +typedef NSArray NSURLArray; ++ (NSURLArray *)NSURLArray:(id)json; + +typedef NSArray NSNumberArray; ++ (NSNumberArray *)NSNumberArray:(id)json; + +typedef NSArray UIColorArray; ++ (UIColorArray *)UIColorArray:(id)json; + +typedef NSArray CGColorArray; ++ (CGColorArray *)CGColorArray:(id)json; + +typedef BOOL css_overflow; ++ (css_overflow)css_overflow:(id)json; + (css_flex_direction_t)css_flex_direction_t:(id)json; + (css_justify_t)css_justify_t:(id)json; + (css_align_t)css_align_t:(id)json; @@ -195,7 +207,7 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) * This macro is used for creating converter functions for typed arrays. */ #define RCT_ARRAY_CONVERTER(type) \ -+ (NSArray *)type##Array:(id)json \ ++ (type##Array *)type##Array:(id)json \ { \ NSMutableArray *values = [[NSMutableArray alloc] init]; \ for (id jsonValue in [self NSArray:json]) { \ diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 1ddc9884c..a46379ae8 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -670,11 +670,6 @@ static BOOL RCTFontIsCondensed(UIFont *font) isCondensed = RCTFontIsCondensed(font); } - // Get font weight - if (weight) { - fontWeight = [self RCTFontWeight:weight]; - } - // Get font style if (style) { isItalic = [self RCTFontStyle:style]; @@ -700,6 +695,11 @@ static BOOL RCTFontIsCondensed(UIFont *font) } } + // Get font weight + if (weight) { + fontWeight = [self RCTFontWeight:weight]; + } + // Get closest match UIFont *bestMatch = font; CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY; @@ -742,8 +742,6 @@ RCT_ARRAY_CONVERTER(UIColor) return colors; } -typedef BOOL css_overflow; - RCT_ENUM_CONVERTER(css_overflow, (@{ @"hidden": @NO, @"visible": @YES diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 5fe58f608..fdb79b8fb 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -12,6 +12,7 @@ #import "RCTRedBox.h" #import "RCTRootView.h" #import "RCTSourceCode.h" +#import "RCTWebViewExecutor.h" @interface RCTDevMenu () { BOOL _liveReload; @@ -33,13 +34,14 @@ - (void)show { - NSString *debugTitle = self.bridge.executorClass == Nil ? @"Enable Debugging" : @"Disable Debugging"; + NSString *debugTitleChrome = self.bridge.executorClass != Nil && self.bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; + NSString *debugTitleSafari = self.bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload"; UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil - otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil]; + otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, nil]; actionSheet.actionSheetStyle = UIBarStyleBlack; [actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]]; } @@ -49,9 +51,14 @@ if (buttonIndex == 0) { [self.bridge reload]; } else if (buttonIndex == 1) { - self.bridge.executorClass = self.bridge.executorClass == Nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil; + Class cls = NSClassFromString(@"RCTWebSocketExecutor"); + self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : nil; [self.bridge reload]; } else if (buttonIndex == 2) { + Class cls = [RCTWebViewExecutor class]; + self.bridge.executorClass = (self.bridge.executorClass != cls) ? cls : Nil; + [self.bridge reload]; + } else if (buttonIndex == 3) { _liveReload = !_liveReload; [self _pollAndReload]; } @@ -60,7 +67,7 @@ - (void)_pollAndReload { if (_liveReload) { - RCTSourceCode *sourceCodeModule = self.bridge.modules[NSStringFromClass([RCTSourceCode class])]; + RCTSourceCode *sourceCodeModule = self.bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; NSURL *url = sourceCodeModule.scriptURL; NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url]; [self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL]; diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index a83ce58cc..c82eca01c 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -25,14 +25,9 @@ return self; } -+ (NSArray *)JSMethods -{ - return @[ - @"RCTNativeAppEventEmitter.emit", - @"RCTDeviceEventEmitter.emit", - @"RCTEventEmitter.receiveEvent", - ]; -} +RCT_IMPORT_METHOD(RCTNativeAppEventEmitter, emit); +RCT_IMPORT_METHOD(RCTDeviceEventEmitter, emit); +RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent); - (void)sendAppEventWithName:(NSString *)name body:(id)body { diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 1d61946b9..518916c92 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -104,15 +104,20 @@ if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) { NSDictionary *userInfo; NSDictionary *errorDetails = RCTJSONParse(rawText, nil); - if ([errorDetails isKindOfClass:[NSDictionary class]]) { + if ([errorDetails isKindOfClass:[NSDictionary class]] && + [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { + NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; + for (NSDictionary *err in errorDetails[@"errors"]) { + [fakeStack addObject: @{ + @"methodName": err[@"description"] ?: @"", + @"file": err[@"filename"] ?: @"", + @"lineNumber": err[@"lineNumber"] ?: @0 + }]; + } userInfo = @{ - NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", - @"stack": @[@{ - @"methodName": errorDetails[@"description"] ?: @"", - @"file": errorDetails[@"filename"] ?: @"", - @"lineNumber": errorDetails[@"lineNumber"] ?: @0 - }] - }; + NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", + @"stack": fakeStack, + }; } else { userInfo = @{NSLocalizedDescriptionKey: rawText}; } @@ -123,7 +128,7 @@ onComplete(error); return; } - RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])]; + RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; sourceCodeModule.scriptURL = scriptURL; sourceCodeModule.scriptText = rawText; diff --git a/React/Base/RCTLog.h b/React/Base/RCTLog.h index c30da141b..bc19448ac 100644 --- a/React/Base/RCTLog.h +++ b/React/Base/RCTLog.h @@ -42,8 +42,7 @@ typedef NS_ENUM(NSInteger, RCTLogLevel) { /** * A block signature to be used for custom logging functions. In most cases you * will want to pass these arguments to the RCTFormatLog function in order to - * generate a string, or use the RCTSimpleLogFunction() constructor to register - * a simple function that does not use all of the arguments. + * generate a string. */ typedef void (^RCTLogFunction)( RCTLogLevel level, @@ -65,13 +64,6 @@ NSString *RCTFormatLog( NSString *message ); -/** - * A method to generate a log function from a block with a much simpler - * template. The message passed to the simpler block is equivalent to the - * output of the RCTFormatLog() function. - */ -RCTLogFunction RCTSimpleLogFunction(void (^logFunction)(RCTLogLevel level, NSString *message)); - /** * The default logging function used by RCTLogXX. */ diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index d2de897f1..d5e495d8c 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -116,12 +116,11 @@ NSString *RCTFormatLog( }); [log appendString:[formatter stringFromDate:timestamp]]; } - [log appendString:@"[react]"]; if (level) { [log appendFormat:@"[%s]", RCTLogLevels[level - 1]]; } if (thread) { - NSString *threadName = thread.name; + NSString *threadName = [thread isMainThread] ? @"main" : thread.name; if (threadName.length == 0) { #if DEBUG #pragma clang diagnostic push @@ -149,19 +148,6 @@ NSString *RCTFormatLog( return log; } -RCTLogFunction RCTSimpleLogFunction(void (^logFunction)(RCTLogLevel level, NSString *message)) -{ - return ^(RCTLogLevel level, - NSString *fileName, - NSNumber *lineNumber, - NSString *message) { - - logFunction(level, RCTFormatLog( - [NSDate date], [NSThread currentThread], level, fileName, lineNumber, message - )); - }; -} - void _RCTLogFormat(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...) { if (RCTCurrentLogFunction && level >= RCTCurrentLogThreshold) { @@ -193,9 +179,7 @@ void _RCTLogFormat(RCTLogLevel level, const char *fileName, int lineNumber, NSSt } // Log to JS executor - if ([RCTBridge hasValidJSExecutor]) { - [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; - } + [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; #endif diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index de3c7dda8..f5e8fbbb4 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -172,6 +172,7 @@ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; cell.textLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14]; + cell.textLabel.numberOfLines = 2; cell.detailTextLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7]; cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11]; cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; @@ -196,7 +197,7 @@ CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; return ceil(boundingRect.size.height) + 40; } else { - return 44; + return 50; } } diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 6c15e509c..02d7ef447 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -162,13 +162,8 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; } } -+ (NSArray *)JSMethods -{ - return @[ - @"AppRegistry.runApplication", - @"ReactIOS.unmountComponentAtNodeAndRemoveContainer" - ]; -} +RCT_IMPORT_METHOD(AppRegistry, runApplication) +RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) - (void)bundleFinishedLoading { @@ -176,9 +171,9 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; _registered = YES; NSString *moduleName = _moduleName ?: @""; NSDictionary *appParameters = @{ - @"rootTag": _contentView.reactTag, - @"initialProps": self.initialProperties ?: @{}, - }; + @"rootTag": _contentView.reactTag, + @"initialProps": self.initialProperties ?: @{}, + }; [_bridge.uiManager registerRootView:_contentView]; [_bridge enqueueJSCall:@"AppRegistry.runApplication" args:@[moduleName, appParameters]]; diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index b5d3b1e72..d409ec3e5 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -200,10 +200,7 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) { reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS } -+ (NSArray *)JSMethods -{ - return @[@"RCTEventEmitter.receiveTouches"]; -} +RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches); /** * Constructs information about touch events to send across the serialized diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 4a6336e20..d20ba8a5f 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -45,9 +45,6 @@ void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement); BOOL RCTClassOverridesClassMethod(Class cls, SEL selector); BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector); -// Enumerate all classes that conform to NSObject protocol -void RCTEnumerateClasses(void (^block)(Class cls)); - // Creates a standardized error object // TODO(#6472857): create NSErrors and automatically convert them over the bridge. NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 300f6d721..cea45c324 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -183,31 +183,6 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector) return NO; } -void RCTEnumerateClasses(void (^block)(Class cls)) -{ - static Class *classes; - static unsigned int classCount; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - classes = objc_copyClassList(&classCount); - }); - - for (unsigned int i = 0; i < classCount; i++) - { - Class cls = classes[i]; - Class superclass = cls; - while (superclass) - { - if (class_conformsToProtocol(superclass, @protocol(NSObject))) - { - block(cls); - break; - } - superclass = class_getSuperclass(superclass); - } - } -} - NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData) { if (toStringify) { diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index a31a41a8e..e50fff904 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -125,6 +125,8 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) RCTAssert(onComplete != nil, @""); _onApplicationScriptLoaded = onComplete; + script = [script stringByReplacingOccurrencesOfString:@"" withString:@""]; if (_objectsToInject.count > 0) { NSMutableString *scriptWithInjections = [[NSMutableString alloc] initWithString:@"/* BEGIN NATIVELY INJECTED OBJECTS */\n"]; [_objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *blockScript, BOOL *stop) { diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index bda7c357e..ae11ce52b 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -23,6 +23,8 @@ NSMutableArray *_alertButtonKeys; } +RCT_EXPORT_MODULE() + - (instancetype)init { if ((self = [super init])) { @@ -47,10 +49,9 @@ * Buttons are displayed in the order they are specified. If "cancel" is used as * the button key, it will be differently highlighted, according to iOS UI conventions. */ -- (void)alertWithArgs:(NSDictionary *)args callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - NSString *title = args[@"title"]; NSString *message = args[@"message"]; NSArray *buttons = args[@"buttons"]; diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m index ab44a70a3..9743e5042 100644 --- a/React/Modules/RCTAppState.m +++ b/React/Modules/RCTAppState.m @@ -35,6 +35,8 @@ static NSString *RCTCurrentAppBackgroundState() @synthesize bridge = _bridge; +RCT_EXPORT_MODULE() + #pragma mark - Lifecycle - (instancetype)init @@ -79,11 +81,9 @@ static NSString *RCTCurrentAppBackgroundState() /** * Get the current background/foreground state of the app */ -- (void)getCurrentAppState:(RCTResponseSenderBlock)callback - error:(__unused RCTResponseSenderBlock)error +RCT_EXPORT_METHOD(getCurrentAppState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) { - RCT_EXPORT(); - callback(@[@{@"app_state": _lastKnownState}]); } diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 95fb383e4..8e6d414cf 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -88,6 +88,8 @@ static dispatch_queue_t RCTFileQueue(void) NSString *_storageDirectory; } +RCT_EXPORT_MODULE() + - (NSString *)_filePathForKey:(NSString *)key { NSString *safeFileName = RCTMD5Hash(key); @@ -186,10 +188,9 @@ static dispatch_queue_t RCTFileQueue(void) #pragma mark - Exported JS Functions -- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(multiGet:(NSArray *)keys + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - if (!callback) { RCTLogError(@"Called getItem without a callback."); return; @@ -212,10 +213,9 @@ static dispatch_queue_t RCTFileQueue(void) }); } -- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - dispatch_async(RCTFileQueue(), ^{ id errorOut = [self _ensureSetup]; if (errorOut) { @@ -234,10 +234,9 @@ static dispatch_queue_t RCTFileQueue(void) }); } -- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - dispatch_async(RCTFileQueue(), ^{ id errorOut = [self _ensureSetup]; if (errorOut) { @@ -261,10 +260,8 @@ static dispatch_queue_t RCTFileQueue(void) }); } -- (void)clear:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - dispatch_async(RCTFileQueue(), ^{ id errorOut = [self _ensureSetup]; if (!errorOut) { @@ -282,10 +279,8 @@ static dispatch_queue_t RCTFileQueue(void) }); } -- (void)getAllKeys:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - dispatch_async(RCTFileQueue(), ^{ id errorOut = [self _ensureSetup]; if (errorOut) { diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 01b701f54..1ce5c7383 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -16,6 +16,8 @@ __weak id _delegate; } +RCT_EXPORT_MODULE() + - (instancetype)initWithDelegate:(id)delegate { if ((self = [super init])) { @@ -29,10 +31,9 @@ return [self initWithDelegate:nil]; } -- (void)reportUnhandledExceptionWithMessage:(NSString *)message stack:(NSArray *)stack +RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message + stack:(NSArray *)stack) { - RCT_EXPORT(reportUnhandledException); - if (_delegate) { [_delegate unhandledJSExceptionWithMessage:message stack:stack]; } else { @@ -40,10 +41,9 @@ } } -- (void)updateExceptionMessage:(NSString *)message stack:(NSArray *)stack +RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message + stack:(NSArray *)stack) { - RCT_EXPORT(updateExceptionMessage); - [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; } diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m index 9ccc0269d..76e9190bc 100644 --- a/React/Modules/RCTSourceCode.m +++ b/React/Modules/RCTSourceCode.m @@ -14,9 +14,11 @@ @implementation RCTSourceCode -- (void)getScriptText:(RCTResponseSenderBlock)successCallback failureCallback:(RCTResponseSenderBlock)failureCallback +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback + failureCallback:(RCTResponseSenderBlock)failureCallback) { - RCT_EXPORT(); if (self.scriptText && self.scriptURL) { successCallback(@[@{@"text": self.scriptText, @"url":[self.scriptURL absoluteString]}]); } else { diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 258ef4e8c..ad8ee1df6 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -24,10 +24,11 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() return value; } -- (void)setStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated -{ - RCT_EXPORT(); +RCT_EXPORT_MODULE() +RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle + animated:(BOOL)animated) +{ dispatch_async(dispatch_get_main_queue(), ^{ if (RCTViewControllerBasedStatusBarAppearance()) { @@ -40,10 +41,9 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() }); } -- (void)setHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation +RCT_EXPORT_METHOD(setHidden:(BOOL)hidden + withAnimation:(UIStatusBarAnimation)animation) { - RCT_EXPORT(); - dispatch_async(dispatch_get_main_queue(), ^{ if (RCTViewControllerBasedStatusBarAppearance()) { diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 5fd46e21c..8c7ef1f23 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -63,10 +63,9 @@ @synthesize bridge = _bridge; -+ (NSArray *)JSMethods -{ - return @[@"RCTJSTimers.callTimers"]; -} +RCT_EXPORT_MODULE() + +RCT_IMPORT_METHOD(RCTJSTimers, callTimers) - (instancetype)init { @@ -166,34 +165,29 @@ * calculating the timer's target time. We calculate this by passing in * Date.now() from JS and then subtracting that from the current time here. */ -- (void)createTimer:(NSNumber *)callbackID - duration:(double)jsDuration - jsSchedulingTime:(double)jsSchedulingTime - repeats:(BOOL)repeats +RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID + duration:(NSTimeInterval)jsDuration + jsSchedulingTime:(NSDate *)jsSchedulingTime + repeats:(BOOL)repeats) { - RCT_EXPORT(); - if (jsDuration == 0 && repeats == NO) { // For super fast, one-off timers, just enqueue them immediately rather than waiting a frame. [_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[@[callbackID]]]; return; } - NSTimeInterval interval = jsDuration / 1000; - NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime / 1000; - NSTimeInterval currentTimeSinceUnixEpoch = [[NSDate date] timeIntervalSince1970]; - NSTimeInterval jsSchedulingOverhead = currentTimeSinceUnixEpoch - jsCreationTimeSinceUnixEpoch; + NSTimeInterval jsSchedulingOverhead = -jsSchedulingTime.timeIntervalSinceNow; if (jsSchedulingOverhead < 0) { RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000)); } - NSTimeInterval targetTime = interval - jsSchedulingOverhead; - if (interval < 0.018) { // Make sure short intervals run each frame - interval = 0; + NSTimeInterval targetTime = jsDuration - jsSchedulingOverhead; + if (jsDuration < 0.018) { // Make sure short intervals run each frame + jsDuration = 0; } RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID - interval:interval + interval:jsDuration targetTime:targetTime repeats:repeats]; dispatch_async(dispatch_get_main_queue(), ^{ @@ -202,10 +196,8 @@ }); } -- (void)deleteTimer:(NSNumber *)timerID +RCT_EXPORT_METHOD(deleteTimer:(NSNumber *)timerID) { - RCT_EXPORT(); - if (timerID) { dispatch_async(dispatch_get_main_queue(), ^{ _timers[timerID] = nil; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index c21adc1d0..1fe672d8b 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -197,6 +197,8 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio @synthesize bridge = _bridge; +RCT_EXPORT_MODULE() + /** * Declared in RCTBridge. */ @@ -209,6 +211,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); static NSString *RCTViewNameForModuleName(NSString *moduleName) { NSString *name = moduleName; + RCTCAssert(name.length, @"Invalid moduleName '%@'", moduleName); if ([name hasSuffix:@"Manager"]) { name = [name substringToIndex:name.length - @"Manager".length]; } @@ -511,10 +514,8 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) * A method to be called from JS, which takes a container ID and then releases * all subviews for that container upon receipt. */ -- (void)removeSubviewsFromContainerWithID:(NSNumber *)containerID +RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(NSNumber *)containerID) { - RCT_EXPORT(); - id container = _shadowViewRegistry[containerID]; RCTAssert(container != nil, @"container view (for ID %@) not found", containerID); @@ -568,10 +569,8 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) } } -- (void)removeRootView:(NSNumber *)rootReactTag +RCT_EXPORT_METHOD(removeRootView:(NSNumber *)rootReactTag) { - RCT_EXPORT(); - RCTShadowView *rootShadowView = _shadowViewRegistry[rootReactTag]; RCTAssert(rootShadowView.superview == nil, @"root view cannot have superview (ID %@)", rootReactTag); [self _purgeChildren:rootShadowView.reactSubviews fromRegistry:_shadowViewRegistry]; @@ -586,10 +585,8 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) }]; } -- (void)replaceExistingNonRootView:(NSNumber *)reactTag withView:(NSNumber *)newReactTag +RCT_EXPORT_METHOD(replaceExistingNonRootView:(NSNumber *)reactTag withView:(NSNumber *)newReactTag) { - RCT_EXPORT(); - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTAssert(shadowView != nil, @"shadowView (for ID %@) not found", reactTag); @@ -608,15 +605,13 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) removeAtIndices:removeAtIndices]; } -- (void)manageChildren:(NSNumber *)containerReactTag - moveFromIndices:(NSArray *)moveFromIndices - moveToIndices:(NSArray *)moveToIndices - addChildReactTags:(NSArray *)addChildReactTags - addAtIndices:(NSArray *)addAtIndices - removeAtIndices:(NSArray *)removeAtIndices +RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices + addChildReactTags:(NSArray *)addChildReactTags + addAtIndices:(NSArray *)addAtIndices + removeAtIndices:(NSArray *)removeAtIndices) { - RCT_EXPORT(); - [self _manageChildren:containerReactTag moveFromIndices:moveFromIndices moveToIndices:moveToIndices @@ -626,7 +621,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) registry:_shadowViewRegistry]; [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ - [uiManager _manageChildren:containerReactTag moveFromIndices:moveFromIndices moveToIndices:moveToIndices @@ -735,12 +729,10 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView [shadowView updateLayout]; } -- (void)createAndRegisterViewWithReactTag:(NSNumber *)reactTag - viewName:(NSString *)viewName - props:(NSDictionary *)props +RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag + viewName:(NSString *)viewName + props:(NSDictionary *)props) { - RCT_EXPORT(createView); - RCTViewManager *manager = _viewManagers[viewName]; if (manager == nil) { RCTLogWarn(@"No manager class found for view with module name \"%@\"", viewName); @@ -792,12 +784,12 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }]; } // TODO: remove viewName param as it isn't needed -- (void)updateView:(NSNumber *)reactTag viewName:(__unused NSString *)_ props:(NSDictionary *)props +RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag + viewName:(__unused NSString *)_ + props:(NSDictionary *)props) { - RCT_EXPORT(); - RCTViewManager *viewManager = _viewManagerRegistry[reactTag]; - NSString *viewName = RCTViewNameForModuleName([[viewManager class] moduleName]); + NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([viewManager class])); RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager); @@ -808,10 +800,8 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }]; } -- (void)becomeResponder:(NSNumber *)reactTag +RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag) { - RCT_EXPORT(focus); - if (!reactTag) return; [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { UIView *newResponder = viewRegistry[reactTag]; @@ -821,10 +811,8 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }]; } -- (void)resignResponder:(NSNumber *)reactTag +RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag) { - RCT_EXPORT(blur); - if (!reactTag) return; [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ UIView *currentResponder = viewRegistry[reactTag]; @@ -889,10 +877,9 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }); } -- (void)measure:(NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(measure:(NSNumber *)reactTag + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - if (!callback) { RCTLogError(@"Called measure with no callback"); return; @@ -965,13 +952,11 @@ static void RCTMeasureLayout(RCTShadowView *view, * anything on the main UI thread. Invokes supplied callback with (x, y, width, * height). */ -- (void)measureLayout:(NSNumber *)reactTag - relativeTo:(NSNumber *)ancestorReactTag - errorCallback:(RCTResponseSenderBlock)errorCallback - callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(measureLayout:(NSNumber *)reactTag + relativeTo:(NSNumber *)ancestorReactTag + errorCallback:(RCTResponseSenderBlock)errorCallback + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag]; RCTMeasureLayout(shadowView, ancestorShadowView, callback); @@ -984,12 +969,10 @@ static void RCTMeasureLayout(RCTShadowView *view, * anything on the main UI thread. Invokes supplied callback with (x, y, width, * height). */ -- (void)measureLayoutRelativeToParent:(NSNumber *)reactTag - errorCallback:(RCTResponseSenderBlock)errorCallback - callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag + errorCallback:(RCTResponseSenderBlock)errorCallback + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTMeasureLayout(shadowView, shadowView.reactSuperview, callback); } @@ -1001,13 +984,11 @@ static void RCTMeasureLayout(RCTShadowView *view, * Only layouts for views that are within the rect passed in are returned. Invokes the error callback if the * passed in parent view does not exist. Invokes the supplied callback with the array of computed layouts. */ -- (void)measureViewsInRect:(NSDictionary *)rect - parentView:(NSNumber *)reactTag - errorCallback:(RCTResponseSenderBlock)errorCallback - callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect + parentView:(NSNumber *)reactTag + errorCallback:(RCTResponseSenderBlock)errorCallback + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; if (!shadowView) { RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag); @@ -1047,10 +1028,8 @@ static void RCTMeasureLayout(RCTShadowView *view, callback(@[results]); } -- (void)setMainScrollViewTag:(NSNumber *)reactTag +RCT_EXPORT_METHOD(setMainScrollViewTag:(NSNumber *)reactTag) { - RCT_EXPORT(); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ // - There should be at most one designated "main scroll view" // - There should be at most one designated "`nativeMainScrollDelegate`" @@ -1073,10 +1052,10 @@ static void RCTMeasureLayout(RCTShadowView *view, }]; } -- (void)scrollToOffsetWithView:(NSNumber *)reactTag scrollToOffsetX:(NSNumber *)offsetX offsetY:(NSNumber *)offsetY +RCT_EXPORT_METHOD(scrollTo:(NSNumber *)reactTag + withOffsetX:(NSNumber *)offsetX + offsetY:(NSNumber *)offsetY) { - RCT_EXPORT(scrollTo); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ UIView *view = viewRegistry[reactTag]; if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { @@ -1087,10 +1066,10 @@ static void RCTMeasureLayout(RCTShadowView *view, }]; } -- (void)scrollWithoutAnimationToOffsetWithView:(NSNumber *)reactTag scrollToOffsetX:(NSNumber *)offsetX offsetY:(NSNumber *)offsetY +RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag + offsetX:(NSNumber *)offsetX + offsetY:(NSNumber *)offsetY) { - RCT_EXPORT(scrollWithoutAnimationTo); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ UIView *view = viewRegistry[reactTag]; if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { @@ -1101,10 +1080,9 @@ static void RCTMeasureLayout(RCTShadowView *view, }]; } -- (void)zoomToRectWithView:(NSNumber *)reactTag rect:(NSDictionary *)rectDict +RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag + withRect:(NSDictionary *)rectDict) { - RCT_EXPORT(zoomToRect); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ UIView *view = viewRegistry[reactTag]; if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { @@ -1119,10 +1097,8 @@ static void RCTMeasureLayout(RCTShadowView *view, * JS sets what *it* considers to be the responder. Later, scroll views can use * this in order to determine if scrolling is appropriate. */ -- (void)setJSResponder:(NSNumber *)reactTag +RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag) { - RCT_EXPORT(); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _jsResponder = viewRegistry[reactTag]; if (!_jsResponder) { @@ -1131,10 +1107,8 @@ static void RCTMeasureLayout(RCTShadowView *view, }]; } -- (void)clearJSResponder +RCT_EXPORT_METHOD(clearJSResponder) { - RCT_EXPORT(); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _jsResponder = nil; }]; @@ -1404,12 +1378,10 @@ static void RCTMeasureLayout(RCTShadowView *view, return allJSConstants; } -- (void)configureNextLayoutAnimation:(NSDictionary *)config - withCallback:(RCTResponseSenderBlock)callback - errorCallback:(RCTResponseSenderBlock)errorCallback +RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config + withCallback:(RCTResponseSenderBlock)callback + errorCallback:(RCTResponseSenderBlock)errorCallback) { - RCT_EXPORT(); - if (_nextLayoutAnimation) { RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation, config); @@ -1417,13 +1389,12 @@ static void RCTMeasureLayout(RCTShadowView *view, if (config[@"delete"] != nil) { RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config); } - _nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config callback:callback]; + _nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config + callback:callback]; } -- (void)startOrResetInteractionTiming +RCT_EXPORT_METHOD(startOrResetInteractionTiming) { - RCT_EXPORT(); - NSSet *rootViewTags = [_rootViewTags copy]; [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { for (NSNumber *reactTag in rootViewTags) { @@ -1435,11 +1406,9 @@ static void RCTMeasureLayout(RCTShadowView *view, }]; } -- (void)endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess - onError:(RCTResponseSenderBlock)onError +RCT_EXPORT_METHOD(endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess + onError:(RCTResponseSenderBlock)onError) { - RCT_EXPORT(); - NSSet *rootViewTags = [_rootViewTags copy]; [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init]; @@ -1449,7 +1418,7 @@ static void RCTMeasureLayout(RCTShadowView *view, timingData[reactTag.stringValue] = [rootView endAndResetInteractionTiming]; } } - onSuccess(@[ timingData ]); + onSuccess(@[timingData]); }]; } @@ -1466,7 +1435,7 @@ static UIView *_jsResponder; - (RCTUIManager *)uiManager { - return self.modules[NSStringFromClass([RCTUIManager class])]; + return self.modules[RCTBridgeModuleNameForClass([RCTUIManager class])]; } @end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 6f99acaab..5c9c13355 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -146,7 +146,6 @@ 13E067501A70F44B002CDEE1 /* RCTView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTView.m; sourceTree = ""; }; 13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = ""; }; 13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = ""; }; - 13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = ""; }; 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = ""; }; 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = ""; }; 14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = ""; }; @@ -366,7 +365,6 @@ 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */, - 13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */, 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */, 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */, 83CBBA4D1A601E3B00E9B192 /* RCTLog.h */, @@ -452,7 +450,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if nc -w 5 -z localhost 8081 ; then\n if ! curl -4 -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n exit 2\n fi\nelse\n open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"\nfi"; + shellScript = "if nc -w 5 -z localhost 8081 ; then\n if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n exit 2\n fi\nelse\n open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"\nfi"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/React/Views/RCTDatePickerManager.m b/React/Views/RCTDatePickerManager.m index 4029998c2..36397d6e5 100644 --- a/React/Views/RCTDatePickerManager.m +++ b/React/Views/RCTDatePickerManager.m @@ -27,6 +27,8 @@ RCT_ENUM_CONVERTER(UIDatePickerMode, (@{ @implementation RCTDatePickerManager +RCT_EXPORT_MODULE() + - (UIView *)view { UIDatePicker *picker = [[UIDatePicker alloc] init]; diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 9bd0f7eab..24d8bee16 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -54,6 +54,8 @@ @implementation RCTMapManager +RCT_EXPORT_MODULE() + - (UIView *)view { RCTMap *map = [[RCTMap alloc] init]; diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m index 549859ae0..b6d38ac00 100644 --- a/React/Views/RCTNavItemManager.m +++ b/React/Views/RCTNavItemManager.m @@ -14,6 +14,8 @@ @implementation RCTNavItemManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTNavItem alloc] init]; diff --git a/React/Views/RCTNavigatorManager.m b/React/Views/RCTNavigatorManager.m index 7df00c764..730380bf9 100644 --- a/React/Views/RCTNavigatorManager.m +++ b/React/Views/RCTNavigatorManager.m @@ -17,6 +17,8 @@ @implementation RCTNavigatorManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTNavigator alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; @@ -34,12 +36,10 @@ RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger) } // TODO: remove error callbacks -- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag - errorCallback:(RCTResponseSenderBlock)errorCallback - callback:(__unused RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag + errorCallback:(RCTResponseSenderBlock)errorCallback + callback:(__unused RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ RCTNavigator *navigator = viewRegistry[reactTag]; if ([navigator isKindOfClass:[RCTNavigator class]]) { diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m index cffacca68..3bbc60b94 100644 --- a/React/Views/RCTPickerManager.m +++ b/React/Views/RCTPickerManager.m @@ -15,6 +15,8 @@ @implementation RCTPickerManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTPicker alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 066d28adc..ededf5f7f 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -17,6 +17,8 @@ @implementation RCTScrollViewManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; @@ -63,11 +65,9 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle) }; } -- (void)getContentSize:(NSNumber *)reactTag - callback:(RCTResponseSenderBlock)callback +RCT_EXPORT_METHOD(getContentSize:(NSNumber *)reactTag + callback:(RCTResponseSenderBlock)callback) { - RCT_EXPORT(); - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { UIView *view = viewRegistry[reactTag]; diff --git a/React/Views/RCTSliderManager.m b/React/Views/RCTSliderManager.m index 0228eab68..58b763b92 100644 --- a/React/Views/RCTSliderManager.m +++ b/React/Views/RCTSliderManager.m @@ -15,6 +15,8 @@ @implementation RCTSliderManager +RCT_EXPORT_MODULE() + - (UIView *)view { UISlider *slider = [[UISlider alloc] init]; diff --git a/React/Views/RCTSwitchManager.m b/React/Views/RCTSwitchManager.m index e2118023d..eb0d626e6 100644 --- a/React/Views/RCTSwitchManager.m +++ b/React/Views/RCTSwitchManager.m @@ -16,6 +16,8 @@ @implementation RCTSwitchManager +RCT_EXPORT_MODULE() + - (UIView *)view { RCTSwitch *switcher = [[RCTSwitch alloc] init]; diff --git a/React/Views/RCTTabBarItemManager.m b/React/Views/RCTTabBarItemManager.m index 7f4ce9642..8bbe782b7 100644 --- a/React/Views/RCTTabBarItemManager.m +++ b/React/Views/RCTTabBarItemManager.m @@ -14,6 +14,8 @@ @implementation RCTTabBarItemManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTTabBarItem alloc] init]; diff --git a/React/Views/RCTTabBarManager.m b/React/Views/RCTTabBarManager.m index 7ff9ef075..c7dfe09e1 100644 --- a/React/Views/RCTTabBarManager.m +++ b/React/Views/RCTTabBarManager.m @@ -16,6 +16,8 @@ @synthesize bridge = _bridge; +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTTabBar alloc] initWithEventDispatcher:_bridge.eventDispatcher]; diff --git a/React/Views/RCTTextFieldManager.m b/React/Views/RCTTextFieldManager.m index 041474643..3cfdd53a1 100644 --- a/React/Views/RCTTextFieldManager.m +++ b/React/Views/RCTTextFieldManager.m @@ -17,6 +17,8 @@ @implementation RCTTextFieldManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTTextField alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; diff --git a/React/Views/RCTUIActivityIndicatorViewManager.m b/React/Views/RCTUIActivityIndicatorViewManager.m index 09a5cb6f9..e2c9b3d35 100644 --- a/React/Views/RCTUIActivityIndicatorViewManager.m +++ b/React/Views/RCTUIActivityIndicatorViewManager.m @@ -24,6 +24,8 @@ RCT_ENUM_CONVERTER(UIActivityIndicatorViewStyle, (@{ @implementation RCTUIActivityIndicatorViewManager +RCT_EXPORT_MODULE(UIActivityIndicatorViewManager) + - (UIView *)view { return [[UIActivityIndicatorView alloc] init]; diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index 32babecc9..17d89dff6 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -30,14 +30,6 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v */ @property (nonatomic, strong) RCTBridge *bridge; -/** - * The module name exposed to React JS. If omitted, this will be inferred - * automatically by using the view module's class name. It is better to not - * override this, and just follow standard naming conventions for your view - * module subclasses. - */ -+ (NSString *)moduleName; - /** * This method instantiates a native view to be managed by the module. Override * this to return a custom view instance, which may be preconfigured with default diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 50d55a8a7..9758a0944 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -21,18 +21,7 @@ @synthesize bridge = _bridge; -+ (NSString *)moduleName -{ - // Default implementation, works in most cases - NSString *name = NSStringFromClass(self); - if ([name hasPrefix:@"RK"]) { - name = [name stringByReplacingCharactersInRange:(NSRange){0,@"RK".length} withString:@"RCT"]; - } - if ([name hasPrefix:@"RCTUI"]) { - name = [name substringFromIndex:@"RCT".length]; - } - return name; -} +RCT_EXPORT_MODULE() - (UIView *)view { diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 7525ee236..e25a7da68 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -16,6 +16,8 @@ @implementation RCTWebViewManager +RCT_EXPORT_MODULE() + - (UIView *)view { return [[RCTWebView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; @@ -41,10 +43,8 @@ RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); }; } -- (void)goBack:(NSNumber *)reactTag +RCT_EXPORT_METHOD(goBack:(NSNumber *)reactTag) { - RCT_EXPORT(); - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { @@ -54,10 +54,8 @@ RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); }]; } -- (void)goForward:(NSNumber *)reactTag +RCT_EXPORT_METHOD(goForward:(NSNumber *)reactTag) { - RCT_EXPORT(); - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { id view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { @@ -68,10 +66,8 @@ RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); } -- (void)reload:(NSNumber *)reactTag +RCT_EXPORT_METHOD(reload:(NSNumber *)reactTag) { - RCT_EXPORT(); - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { diff --git a/package.json b/package.json index eaf176a38..f372f7882 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "start": "./packager/packager.sh" }, "bin": { - "react-native": "local-cli/wrong-react-native.js", "react-native-start": "packager/packager.sh" }, "dependencies": { @@ -62,7 +61,9 @@ "underscore": "1.7.0", "worker-farm": "1.1.0", "ws": "0.4.31", - "yargs": "1.3.2" + "yargs": "1.3.2", + "bluebird": "^2.9.21", + "image-size": "0.3.5" }, "devDependencies": { "jest-cli": "0.2.1", diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js new file mode 100644 index 000000000..e0f98bde2 --- /dev/null +++ b/packager/getFlowTypeCheckMiddleware.js @@ -0,0 +1,86 @@ +/** + * 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'; + +var exec = require('child_process').exec; + +function getFlowTypeCheckMiddleware(options) { + return function(req, res, next) { + if (options.skipflow) { + return next(); + } + if (options.flowroot || options.projectRoots.length === 1) { + var flowroot = options.flowroot || options.projectRoots[0]; + } else { + console.warn('flow: No suitable root'); + return next(); + } + exec('command -v flow >/dev/null 2>&1', function(error, stdout) { + if (error) { + console.warn('flow: Skipping because not installed. Install with ' + + '`brew install flow`.'); + return next(); + } else { + return doFlowTypecheck(res, flowroot, next); + } + }); + }; +} + +function doFlowTypecheck(res, flowroot, next) { + var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; + var start = Date.now(); + console.log('flow: Running static typechecks.'); + exec(flowCmd, function(flowError, stdout) { + if (!flowError) { + console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).'); + return next(); + } else { + try { + var flowResponse = JSON.parse(stdout); + var errors = []; + var errorNum = 1; + flowResponse.errors.forEach(function(err) { + // flow errors are paired across callsites, so we indent and prefix to + // group them + var indent = ''; + err.message.forEach(function(msg) { + errors.push({ + description: indent + 'E' + errorNum + ': ' + msg.descr, + filename: msg.path, + lineNumber: msg.line, + column: msg.start, + }); + indent = ' '; + }); + errorNum++; + }); + var message = 'Flow found type errors. If you think these are wrong, ' + + 'make sure flow is up to date, or disable with --skipflow.'; + } catch (e) { + var message = + 'Flow failed to provide parseable output:\n\n`' + stdout + '`'; + console.error(message, '\nException: `', e, '`\n\n'); + } + var error = { + status: 500, + message: message, + type: 'FlowError', + errors: errors, + }; + console.error('flow: Error running command `' + flowCmd + '`:\n', error); + res.writeHead(error.status, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + res.end(JSON.stringify(error)); + } + }); +} + +module.exports = getFlowTypeCheckMiddleware; diff --git a/packager/packager.js b/packager/packager.js index 55004b7cf..b098f0a31 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -13,6 +13,8 @@ var path = require('path'); var exec = require('child_process').exec; var http = require('http'); +var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); + if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { console.log( '\n' + @@ -40,6 +42,9 @@ var options = parseCommandLine([{ }, { command: 'assetRoots', description: 'specify the root directories of app assets' +}, { + command: 'skipflow', + description: 'Disable flow checks' }]); if (options.projectRoots) { @@ -203,6 +208,7 @@ function runServer( .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) .use(statusPageMiddleware) + .use(getFlowTypeCheckMiddleware(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { @@ -213,5 +219,5 @@ function runServer( .use(connect.compress()) .use(connect.errorHandler()); - return http.createServer(app).listen(options.port, readyCallback); + return http.createServer(app).listen(options.port, '::', readyCallback); } diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index c56593cfa..c46a57e60 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -30,8 +30,13 @@ function ModuleDescriptor(fields) { this.isPolyfill = fields.isPolyfill || false; + this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; this.isAsset = fields.isAsset || false; + if (this.isAsset_DEPRECATED && this.isAsset) { + throw new Error('Cannot be an asset and a deprecated asset'); + } + this.altId = fields.altId; this._fields = fields; diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index b6a978c63..f42f6f8a1 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -92,7 +92,7 @@ describe('DependencyGraph', function() { { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset_DEPRECATED: true }, ]); }); @@ -183,7 +183,7 @@ describe('DependencyGraph', function() { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset: true + isAsset_DEPRECATED: true }, ]); }); @@ -954,7 +954,7 @@ describe('DependencyGraph', function() { { id: 'image!foo', path: '/root/foo.png', dependencies: [], - isAsset: true, + isAsset_DEPRECATED: true, }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 3348907f1..fbc7de712 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -596,7 +596,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ id: 'image!' + name, path: path.resolve(file), - isAsset: true, + isAsset_DEPRECATED: true, dependencies: [], }); } diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index abfae2482..962eb7fe9 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -125,7 +125,7 @@ function formatError(err, filename, source) { function formatGenericError(err, filename) { var msg = 'TransformError: ' + filename + ': ' + err.message; var error = new TransformError(); - var stack = err.stack.split('\n').slice(0, -1); + var stack = (err.stack || '').split('\n').slice(0, -1); stack.push(msg); error.stack = stack.join('\n'); error.message = msg; diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 8f61df972..f4675c471 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,14 +43,21 @@ describe('Packager', function() { }; }); - var packager = new Packager({projectRoots: []}); + var packager = new Packager({projectRoots: ['/root']}); var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, - { id: 'image!img', + { + id: 'image!img', path: '/root/img/img.png', - isAsset: true, + isAsset_DEPRECATED: true, dependencies: [], + }, + { + id: 'new_image.png', + path: '/root/img/new_image.png', + isAsset: true, + dependencies: [] } ]; @@ -74,6 +81,10 @@ describe('Packager', function() { return 'lol ' + code + ' lol'; }); + require('image-size').mockImpl(function(path, cb) { + cb(null, { width: 50, height: 100 }); + }); + return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0]).toEqual([ @@ -96,6 +107,24 @@ describe('Packager', function() { '/root/img/img.png' ]); + var imgModule = { + isStatic: true, + path: '/root/img/new_image.png', + uri: 'img/new_image.png', + width: 50, + height: 100, + }; + + expect(p.addModule.mock.calls[3]).toEqual([ + 'lol module.exports = ' + + JSON.stringify(imgModule) + + '; lol', + 'module.exports = ' + + JSON.stringify(imgModule) + + ';', + '/root/img/new_image.png' + ]); + expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index bf5a635da..cfdd842db 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -18,6 +18,7 @@ var _ = require('underscore'); var Package = require('./Package'); var Activity = require('../Activity'); var declareOpts = require('../lib/declareOpts'); +var imageSize = require('image-size'); var validateOpts = declareOpts({ projectRoots: { @@ -88,6 +89,8 @@ function Packager(options) { transformModulePath: opts.transformModulePath, nonPersistent: opts.nonPersistent, }); + + this._projectRoots = opts.projectRoots; } Packager.prototype.kill = function() { @@ -138,8 +141,13 @@ Packager.prototype.getDependencies = function(main, isDev) { Packager.prototype._transformModule = function(module) { var transform; - if (module.isAsset) { - transform = Promise.resolve(generateAssetModule(module)); + if (module.isAsset_DEPRECATED) { + transform = Promise.resolve(generateAssetModule_DEPRECATED(module)); + } else if (module.isAsset) { + transform = generateAssetModule( + module, + getPathRelativeToRoot(this._projectRoots, module.path) + ); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) @@ -166,7 +174,7 @@ Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; -function generateAssetModule(module) { +function generateAssetModule_DEPRECATED(module) { var code = 'module.exports = ' + JSON.stringify({ uri: module.id.replace(/^[^!]+!/, ''), isStatic: true, @@ -179,4 +187,39 @@ function generateAssetModule(module) { }; } +var sizeOf = Promise.promisify(imageSize); + +function generateAssetModule(module, relPath) { + return sizeOf(module.path).then(function(dimensions) { + var img = { + isStatic: true, + path: module.path, //TODO(amasad): this should be path inside tar file. + uri: relPath, + width: dimensions.width, + height: dimensions.height, + }; + + var code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); +} + +function getPathRelativeToRoot(roots, absPath) { + for (var i = 0; i < roots.length; i++) { + var relPath = path.relative(roots[i], absPath); + if (relPath[0] !== '.') { + return relPath; + } + } + + throw new Error( + 'Expected root module to be relative to one of the project roots' + ); +} + module.exports = Packager; diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 655075815..617359bf8 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -320,6 +320,12 @@ function handleError(res, error) { }); if (error.type === 'TransformError' || error.type === 'NotFoundError') { + error.errors = [{ + description: error.description, + filename: error.filename, + lineNumber: error.lineNumber, + }]; + console.error(error); res.end(JSON.stringify(error)); } else { console.error(error.stack || error);