From 3b11b9d6c3b59dfd9d87f540a4db99436a6ef577 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sun, 1 Mar 2015 15:33:55 -0800 Subject: [PATCH] [WIP] Migrated View Managers over to new architecture --- .../UIExplorer.xcodeproj/project.pbxproj | 166 ++--- .../Components/Navigation/NavigatorIOS.ios.js | 4 +- Libraries/GeoLocation/GeoLocation.js | 2 +- Libraries/Image/RCTNetworkImageView.h | 3 +- Libraries/Image/RCTNetworkImageView.m | 5 - Libraries/Network/RCTDataManager.m | 3 +- .../RCTDataManager.xcodeproj/project.pbxproj | 8 + Libraries/Text/RCTTextManager.m | 50 +- ReactKit/Base/RCTBridge.h | 46 +- ReactKit/Base/RCTBridge.m | 654 ++++++++---------- ReactKit/Base/RCTBridgeModule.h | 24 +- ReactKit/Base/RCTJavaScriptExecutor.h | 2 +- ReactKit/Base/RCTLog.h | 51 +- ReactKit/Base/RCTLog.m | 36 +- ReactKit/Base/RCTRedBox.m | 47 +- ReactKit/Base/RCTRootView.m | 16 +- ReactKit/Base/RCTTouchHandler.h | 2 +- ReactKit/Base/RCTTouchHandler.m | 5 - ReactKit/Base/RCTUtils.h | 19 +- ReactKit/Base/RCTUtils.m | 25 + ReactKit/Base/RCTViewNodeProtocol.h | 4 +- ReactKit/Executors/RCTContextExecutor.m | 2 +- ReactKit/Modules/RCTAlertManager.m | 6 +- ReactKit/Modules/RCTLocationObserver.m | 13 +- ReactKit/Modules/RCTStatusBarManager.m | 2 +- ReactKit/Modules/RCTTiming.m | 30 +- ReactKit/Modules/RCTUIManager.h | 30 + ReactKit/Modules/RCTUIManager.m | 370 +++++----- ReactKit/Views/RCTNavigator.h | 2 +- ReactKit/Views/RCTNavigator.m | 34 +- ReactKit/Views/RCTNavigatorManager.m | 25 +- ReactKit/Views/RCTScrollView.h | 4 +- ReactKit/Views/RCTScrollView.m | 8 +- ReactKit/Views/RCTScrollViewManager.m | 17 +- ReactKit/Views/RCTShadowView.h | 46 +- ReactKit/Views/RCTShadowView.m | 104 +-- ReactKit/Views/RCTStaticImageManager.m | 6 +- ReactKit/Views/RCTTextField.h | 2 +- ReactKit/Views/RCTTextField.m | 9 +- ReactKit/Views/RCTTextFieldManager.m | 29 +- .../Views/RCTUIActivityIndicatorViewManager.m | 9 +- ReactKit/Views/RCTView.h | 1 - ReactKit/Views/RCTView.m | 8 +- ReactKit/Views/RCTViewManager.h | 55 +- ReactKit/Views/RCTViewManager.m | 88 +-- ReactKit/Views/RCTWrapperViewController.h | 7 +- ReactKit/Views/RCTWrapperViewController.m | 5 - 47 files changed, 991 insertions(+), 1093 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 798c95a21..493310f0c 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -7,46 +7,46 @@ objects = { /* Begin PBXBuildFile section */ + 1316A21A1AA397CA00C0188E /* libRCTNetworkImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1316A2101AA3871A00C0188E /* libRCTNetworkImage.a */; }; + 1316A21B1AA397CA00C0188E /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1316A2081AA386C700C0188E /* libRCTText.a */; }; + 1316A21C1AA397CA00C0188E /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1316A2171AA3875D00C0188E /* libReactKit.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 587651361A9EB175008B8F17 /* libRCTNetworkImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 587651281A9EB168008B8F17 /* libRCTNetworkImage.a */; }; - 587651371A9EB175008B8F17 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 587651301A9EB168008B8F17 /* libRCTText.a */; }; - 587651381A9EB175008B8F17 /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 587651351A9EB168008B8F17 /* libReactKit.a */; }; 587651571A9F862F008B8F17 /* libRCTDataManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5876514F1A9F8619008B8F17 /* libRCTDataManager.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 587651271A9EB168008B8F17 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5115D1A9E6B3D00147676; - remoteInfo = RCTNetworkImage; - }; - 587651291A9EB168008B8F17 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B511681A9E6B3D00147676; - remoteInfo = RCTNetworkImageTests; - }; - 5876512F1A9EB168008B8F17 /* PBXContainerItemProxy */ = { + 1316A2071AA386C700C0188E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */; proxyType = 2; remoteGlobalIDString = 58B5119B1A9E6C1200147676; remoteInfo = RCTText; }; - 587651311A9EB168008B8F17 /* PBXContainerItemProxy */ = { + 1316A2091AA386C700C0188E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */; proxyType = 2; remoteGlobalIDString = 58B511A61A9E6C1300147676; remoteInfo = RCTTextTests; }; - 587651341A9EB168008B8F17 /* PBXContainerItemProxy */ = { + 1316A20F1AA3871A00C0188E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTNetworkImage; + }; + 1316A2111AA3871A00C0188E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511681A9E6B3D00147676; + remoteInfo = RCTNetworkImageTests; + }; + 1316A2161AA3875D00C0188E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */; proxyType = 2; @@ -77,9 +77,9 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetworkImage.xcodeproj; path = "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/RCTStandardLibrary/../Image/RCTNetworkImage.xcodeproj"; sourceTree = ""; }; - 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/RCTStandardLibrary/../Text/RCTText.xcodeproj"; sourceTree = ""; }; - 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/RCTStandardLibrary/../../ReactKit/ReactKit.xcodeproj"; sourceTree = ""; }; + 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetworkImage.xcodeproj; path = ../../Libraries/Image/RCTNetworkImage.xcodeproj; sourceTree = SOURCE_ROOT; }; + 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = SOURCE_ROOT; }; + 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = SOURCE_ROOT; }; 587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTDataManager.xcodeproj; path = ../../Libraries/Network/RCTDataManager.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -88,16 +88,53 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1316A21A1AA397CA00C0188E /* libRCTNetworkImage.a in Frameworks */, + 1316A21B1AA397CA00C0188E /* libRCTText.a in Frameworks */, + 1316A21C1AA397CA00C0188E /* libReactKit.a in Frameworks */, 587651571A9F862F008B8F17 /* libRCTDataManager.a in Frameworks */, - 587651361A9EB175008B8F17 /* libRCTNetworkImage.a in Frameworks */, - 587651371A9EB175008B8F17 /* libRCTText.a in Frameworks */, - 587651381A9EB175008B8F17 /* libReactKit.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1316A2031AA386C700C0188E /* Products */ = { + isa = PBXGroup; + children = ( + 1316A2081AA386C700C0188E /* libRCTText.a */, + 1316A20A1AA386C700C0188E /* RCTTextTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 1316A20B1AA3871A00C0188E /* Products */ = { + isa = PBXGroup; + children = ( + 1316A2101AA3871A00C0188E /* libRCTNetworkImage.a */, + 1316A2121AA3871A00C0188E /* RCTNetworkImageTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 1316A2131AA3875D00C0188E /* Products */ = { + isa = PBXGroup; + children = ( + 1316A2171AA3875D00C0188E /* libReactKit.a */, + ); + name = Products; + sourceTree = ""; + }; + 1316A21D1AA397F400C0188E /* Libraries */ = { + isa = PBXGroup; + children = ( + 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */, + 587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */, + 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */, + 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; 13B07FAE1A68108700A75B9A /* UIExplorer */ = { isa = PBXGroup; children = ( @@ -111,32 +148,6 @@ name = UIExplorer; sourceTree = ""; }; - 5876511D1A9EB168008B8F17 /* Products */ = { - isa = PBXGroup; - children = ( - 587651281A9EB168008B8F17 /* libRCTNetworkImage.a */, - 5876512A1A9EB168008B8F17 /* RCTNetworkImageTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 587651201A9EB168008B8F17 /* Products */ = { - isa = PBXGroup; - children = ( - 587651301A9EB168008B8F17 /* libRCTText.a */, - 587651321A9EB168008B8F17 /* RCTTextTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 587651231A9EB168008B8F17 /* Products */ = { - isa = PBXGroup; - children = ( - 587651351A9EB168008B8F17 /* libReactKit.a */, - ); - name = Products; - sourceTree = ""; - }; 5876514A1A9F8619008B8F17 /* Products */ = { isa = PBXGroup; children = ( @@ -150,10 +161,7 @@ isa = PBXGroup; children = ( 13B07FAE1A68108700A75B9A /* UIExplorer */, - 587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */, - 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */, - 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */, - 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */, + 1316A21D1AA397F400C0188E /* Libraries */, 83CBBA001A601CBA00E9B192 /* Products */, ); sourceTree = ""; @@ -212,15 +220,15 @@ ProjectRef = 587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */; }, { - ProductGroup = 5876511D1A9EB168008B8F17 /* Products */; + ProductGroup = 1316A20B1AA3871A00C0188E /* Products */; ProjectRef = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */; }, { - ProductGroup = 587651201A9EB168008B8F17 /* Products */; + ProductGroup = 1316A2031AA386C700C0188E /* Products */; ProjectRef = 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */; }, { - ProductGroup = 587651231A9EB168008B8F17 /* Products */; + ProductGroup = 1316A2131AA3875D00C0188E /* Products */; ProjectRef = 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */; }, ); @@ -232,39 +240,39 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 587651281A9EB168008B8F17 /* libRCTNetworkImage.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTNetworkImage.a; - remoteRef = 587651271A9EB168008B8F17 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 5876512A1A9EB168008B8F17 /* RCTNetworkImageTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = RCTNetworkImageTests.xctest; - remoteRef = 587651291A9EB168008B8F17 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 587651301A9EB168008B8F17 /* libRCTText.a */ = { + 1316A2081AA386C700C0188E /* libRCTText.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libRCTText.a; - remoteRef = 5876512F1A9EB168008B8F17 /* PBXContainerItemProxy */; + remoteRef = 1316A2071AA386C700C0188E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 587651321A9EB168008B8F17 /* RCTTextTests.xctest */ = { + 1316A20A1AA386C700C0188E /* RCTTextTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = RCTTextTests.xctest; - remoteRef = 587651311A9EB168008B8F17 /* PBXContainerItemProxy */; + remoteRef = 1316A2091AA386C700C0188E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 587651351A9EB168008B8F17 /* libReactKit.a */ = { + 1316A2101AA3871A00C0188E /* libRCTNetworkImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetworkImage.a; + remoteRef = 1316A20F1AA3871A00C0188E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1316A2121AA3871A00C0188E /* RCTNetworkImageTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RCTNetworkImageTests.xctest; + remoteRef = 1316A2111AA3871A00C0188E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 1316A2171AA3875D00C0188E /* libReactKit.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libReactKit.a; - remoteRef = 587651341A9EB168008B8F17 /* PBXContainerItemProxy */; + remoteRef = 1316A2161AA3875D00C0188E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 5876514F1A9F8619008B8F17 /* libRCTDataManager.a */ = { diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index efd983203..527b10bd2 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -8,7 +8,7 @@ var EventEmitter = require('EventEmitter'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); -var { RKUIManager } = require('NativeModules'); +var { RKNavigatorManager } = require('NativeModules'); var StyleSheet = require('StyleSheet'); var StaticContainer = require('StaticContainer.react'); var View = require('View'); @@ -127,7 +127,7 @@ var RKNavigatorItem = createReactIOSNativeComponentClass({ var NavigatorTransitionerIOS = React.createClass({ requestSchedulingNavigation: function(cb) { - RKUIManager.requestSchedulingJavaScriptNavigation( + RKNavigatorManager.requestSchedulingJavaScriptNavigation( this.getNodeHandle(), logError, cb diff --git a/Libraries/GeoLocation/GeoLocation.js b/Libraries/GeoLocation/GeoLocation.js index 589931ee9..9a7f792c4 100644 --- a/Libraries/GeoLocation/GeoLocation.js +++ b/Libraries/GeoLocation/GeoLocation.js @@ -6,7 +6,7 @@ 'use strict'; var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var RCTLocationObserver = require('NativeModules').RCTLocationObserver; +var RCTLocationObserver = require('NativeModules').RKLocationObserver; var invariant = require('invariant'); var logError = require('logError'); diff --git a/Libraries/Image/RCTNetworkImageView.h b/Libraries/Image/RCTNetworkImageView.h index c99ed0689..920bf705c 100644 --- a/Libraries/Image/RCTNetworkImageView.h +++ b/Libraries/Image/RCTNetworkImageView.h @@ -6,7 +6,8 @@ @interface RCTNetworkImageView : UIView -- (instancetype)initWithFrame:(CGRect)frame imageDownloader:(RCTImageDownloader *)imageDownloader; +- (instancetype)initWithFrame:(CGRect)frame + imageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER; /** * An image that will appear while the view is loading the image from the network, diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m index c0c6443cb..027fbf500 100644 --- a/Libraries/Image/RCTNetworkImageView.m +++ b/Libraries/Image/RCTNetworkImageView.m @@ -27,11 +27,6 @@ return self; } -- (instancetype)initWithFrame:(CGRect)frame -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (NSURL *)imageURL { // We clear our backing layer's imageURL when we are not in a window for a while, diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTDataManager.m index 22872fa91..4ffe5f975 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTDataManager.m @@ -65,8 +65,7 @@ } else { - RCTLogMustFix(@"unsupported query type %@", queryType); - return; + RCTLogError(@"unsupported query type %@", queryType); } } diff --git a/Libraries/Network/RCTDataManager.xcodeproj/project.pbxproj b/Libraries/Network/RCTDataManager.xcodeproj/project.pbxproj index b7cfed2a2..328a3d60c 100644 --- a/Libraries/Network/RCTDataManager.xcodeproj/project.pbxproj +++ b/Libraries/Network/RCTDataManager.xcodeproj/project.pbxproj @@ -269,6 +269,10 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../ReactKit/**", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos", + ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -283,6 +287,10 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../ReactKit/**", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos", + ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index fe1ca6820..ffe0d8b1e 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -24,17 +24,7 @@ } RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor) - -- (void)set_textAlign:(id)json - forShadowView:(RCTShadowText *)shadowView - withDefaultView:(RCTShadowText *)defaultView -{ - shadowView.textAlign = json ? [RCTConvert NSTextAlignment:json] : defaultView.textAlign; -} - -- (void)set_numberOfLines:(id)json - forView:(RCTText *)view - withDefaultView:(RCTText *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(numberOfLines, RCTText *) { NSLineBreakMode truncationMode = NSLineBreakByClipping; view.numberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.numberOfLines; @@ -44,31 +34,33 @@ RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor) view.lineBreakMode = truncationMode; } -- (void)set_numberOfLines:(id)json - forShadowView:(RCTShadowText *)shadowView - withDefaultView:(RCTShadowText *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, RCTShadowText *) +{ + view.textBackgroundColor = json ? [RCTConvert UIColor:json] : defaultView.textBackgroundColor; +} +RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, RCTShadowText *) +{ + view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor; + view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet; +} +RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, RCTShadowText *) { NSLineBreakMode truncationMode = NSLineBreakByClipping; - shadowView.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines; - if (shadowView.maxNumberOfLines > 0) { + view.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines; + if (view.maxNumberOfLines > 0) { truncationMode = NSLineBreakByTruncatingTail; } - shadowView.truncationMode = truncationMode; + view.truncationMode = truncationMode; +} +RCT_CUSTOM_SHADOW_PROPERTY(textAlign, RCTShadowText *) +{ + view.textAlign = json ? [RCTConvert NSTextAlignment:json] : defaultView.textAlign; } -- (void)set_backgroundColor:(id)json - forShadowView:(RCTShadowText *)shadowView - withDefaultView:(RCTShadowText *)defaultView +- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView { - shadowView.textBackgroundColor = json ? [RCTConvert UIColor:json] : defaultView.textBackgroundColor; -} - -- (void)set_containerBackgroundColor:(id)json - forShadowView:(RCTShadowText *)shadowView - withDefaultView:(RCTShadowText *)defaultView -{ - shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor; - shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet; + //TODO: This could be a cleaner replacement for uiBlockToAmendWithShadowViewRegistry + return nil; } // TODO: the purpose of this block is effectively just to copy properties from the shadow views diff --git a/ReactKit/Base/RCTBridge.h b/ReactKit/Base/RCTBridge.h index 9a890a07a..4dcaee8e2 100644 --- a/ReactKit/Base/RCTBridge.h +++ b/ReactKit/Base/RCTBridge.h @@ -6,32 +6,16 @@ @class RCTBridge; @class RCTEventDispatcher; -@class RCTRootView; - -/** - * Utilities for constructing common response objects. When sending a - * systemError back to JS, it's important to describe whether or not it was a - * system error, or API usage error. System errors should never happen and are - * therefore logged using `RCTLogError()`. API usage errors are expected if the - * API is misused and will therefore not be logged using `RCTLogError()`. The JS - * application code is expected to handle them. Regardless of type, each error - * should be logged at most once. - */ -static inline NSDictionary *RCTSystemErrorObject(NSString *msg) -{ - return @{@"systemError": msg ?: @""}; -} - -static inline NSDictionary *RCTAPIErrorObject(NSString *msg) -{ - return @{@"apiError": msg ?: @""}; -} /** * This block can be used to instantiate modules that require additional * init parameters, or additional configuration prior to being used. + * The bridge will call this block to instatiate the modules, and will + * be responsible for invalidating/releasing them when the bridge is destroyed. + * For this reason, the block should always return new module instances, and + * module instances should not be shared between bridges. */ -typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge); +typedef NSArray *(^RCTBridgeModuleProviderBlock)(void); /** * Async batched bridge used to communicate with the JavaScript application. @@ -42,12 +26,12 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge); * The designated initializer. This creates a new bridge on top of the specified * executor. The bridge should then be used for all subsequent communication * with the JavaScript code running in the executor. Modules will be automatically - * instantiated using the default contructor, but you can optionally pass in a - * module provider block to manually instantiate modules that require additional - * init parameters or configuration. + * instantiated using the default contructor, but you can optionally pass in an + * array of pre-initialized module instances if they require additional init + * parameters or configuration. */ -- (instancetype)initWithJavaScriptExecutor:(id)javaScriptExecutor - moduleProvider:(RCTBridgeModuleProviderBlock)block NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithExecutor:(id)executor + moduleProvider:(RCTBridgeModuleProviderBlock)block NS_DESIGNATED_INITIALIZER; /** * This method is used to call functions in the JavaScript application context. @@ -81,16 +65,6 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge); */ @property (nonatomic, readonly) dispatch_queue_t shadowQueue; -// For use in implementing delegates, which may need to queue responses. -- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID; - -/** - * Register a root view with the bridge. Theorectically, a single bridge can - * support multiple root views, however this feature is not currently exposed - * and may eventually be removed. - */ -- (void)registerRootView:(RCTRootView *)rootView; - /** * Global logging function that will print to both xcode and JS debugger consoles. * diff --git a/ReactKit/Base/RCTBridge.m b/ReactKit/Base/RCTBridge.m index 97a5dc12e..5173bc5d2 100644 --- a/ReactKit/Base/RCTBridge.m +++ b/ReactKit/Base/RCTBridge.m @@ -3,11 +3,12 @@ #import "RCTBridge.h" #import -#import -#import #import #import +#import +#import + #import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTLog.h" @@ -26,60 +27,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldFlushDateMillis }; -/** - * This private class is used as a container for exported method info - */ -@interface RCTModuleMethod : NSObject - -@property (readonly, nonatomic, assign) SEL selector; -@property (readonly, nonatomic, copy) NSString *JSMethodName; -@property (readonly, nonatomic, assign) NSUInteger arity; -@property (readonly, nonatomic, copy) NSIndexSet *blockArgumentIndexes; - -@end - -@implementation RCTModuleMethod - -- (instancetype)initWithSelector:(SEL)selector - JSMethodName:(NSString *)JSMethodName - arity:(NSUInteger)arity - blockArgumentIndexes:(NSIndexSet *)blockArgumentIndexes -{ - if ((self = [super init])) { - _selector = selector; - _JSMethodName = [JSMethodName copy]; - _arity = arity; - _blockArgumentIndexes = [blockArgumentIndexes copy]; - } - return self; -} - -- (NSString *)description -{ - NSString *blocks = @"no block args"; - if (self.blockArgumentIndexes.count > 0) { - NSMutableString *indexString = [NSMutableString string]; - [self.blockArgumentIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - [indexString appendFormat:@", %tu", idx]; - }]; - blocks = [NSString stringWithFormat:@"block args at %@", [indexString substringFromIndex:2]]; - } - - return [NSString stringWithFormat:@"<%@: %p; exports -%@ as %@; %@>", NSStringFromClass(self.class), self, NSStringFromSelector(self.selector), self.JSMethodName, blocks]; -} - -@end - -#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 - /** * This function returns the module name for a given class. */ @@ -88,18 +35,6 @@ static NSString *RCTModuleNameForClass(Class cls) return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls); } -/** - * This function instantiates a new module instance. - */ -static id RCTCreateModuleInstance(Class cls, RCTBridge *bridge) -{ - if ([cls instancesRespondToSelector:@selector(initWithBridge:)]) { - return [[cls alloc] initWithBridge:bridge]; - } else { - return [[cls alloc] init]; - } -} - /** * This function scans all classes available at runtime and returns an array * of all JSMethods registered. @@ -111,22 +46,11 @@ static NSArray *RCTJSMethods(void) dispatch_once(&onceToken, ^{ NSMutableSet *uniqueMethods = [NSMutableSet set]; - unsigned int classCount; - Class *classes = objc_copyClassList(&classCount); - for (unsigned int i = 0; i < classCount; i++) { - - Class cls = classes[i]; - - if (!class_getSuperclass(cls)) { - // Class has no superclass - it's probably something weird - continue; - } - + RCTEnumerateClasses(^(__unsafe_unretained Class cls) { if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) { [uniqueMethods addObjectsFromArray:[cls JSMethods]]; } - } - free(classes); + }); JSMethods = [uniqueMethods allObjects]; }); @@ -147,38 +71,216 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) modules = [NSMutableArray array]; RCTModuleNamesByID = [NSMutableArray array]; - unsigned int classCount; - Class *classes = objc_copyClassList(&classCount); - for (unsigned int i = 0; i < classCount; i++) { + RCTEnumerateClasses(^(__unsafe_unretained Class cls) { + if ([cls conformsToProtocol:@protocol(RCTBridgeModule)]) { - Class cls = classes[i]; + // Add module + [(NSMutableArray *)modules addObject:cls]; - if (!class_getSuperclass(cls)) { - // Class has no superclass - it's probably something weird - continue; + // Add module name + NSString *moduleName = RCTModuleNameForClass(cls); + [(NSMutableArray *)RCTModuleNamesByID addObject:moduleName]; } - - if (![cls conformsToProtocol:@protocol(RCTBridgeModule)]) { - // Not an RCTBridgeModule - continue; - } - - // Add module - [(NSMutableArray *)modules addObject:cls]; - - // Add module name - NSString *moduleName = RCTModuleNameForClass(cls); - [(NSMutableArray *)RCTModuleNamesByID addObject:moduleName]; - } - free(classes); + }); modules = [modules copy]; RCTModuleNamesByID = [RCTModuleNamesByID copy]; }); - + return modules; } +@interface RCTBridge () + +- (void)_invokeAndProcessModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args; + +@end + +/** + * This private class is used as a container for exported method info + */ +@interface RCTModuleMethod : NSObject + +@property (nonatomic, copy, readonly) NSString *moduleClassName; +@property (nonatomic, copy, readonly) NSString *JSMethodName; + +@end + +@implementation RCTModuleMethod +{ + BOOL _isClassMethod; + Class _moduleClass; + SEL _selector; + NSMethodSignature *_methodSignature; + NSArray *_argumentBlocks; + NSString *_methodName; +} + +- (instancetype)initWithMethodName:(NSString *)methodName + JSMethodName:(NSString *)JSMethodName +{ + if ((self = [super init])) { + + _methodName = methodName; + NSArray *parts = [[methodName substringWithRange:NSMakeRange(2, methodName.length - 3)] componentsSeparatedByString:@" "]; + + // Parse class and method + _moduleClassName = parts[0]; + NSRange categoryRange = [_moduleClassName rangeOfString:@"("]; + if (categoryRange.length) + { + _moduleClassName = [_moduleClassName substringToIndex:categoryRange.location]; + } + + // Extract class and method details + _isClassMethod = [methodName characterAtIndex:0] == '+'; + _moduleClass = NSClassFromString(_moduleClassName); + _selector = NSSelectorFromString(parts[1]); + _JSMethodName = JSMethodName ?: [NSStringFromSelector(_selector) componentsSeparatedByString:@":"][0]; + +#if DEBUG + + // Sanity check + RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], + @"You are attempting to export the method %@, but %@ does not \ + conform to the RCTBridgeModule Protocol", methodName, _moduleClassName); +#endif + + // Get method signature + _methodSignature = _isClassMethod ? + [_moduleClass methodSignatureForSelector:_selector] : + [_moduleClass instanceMethodSignatureForSelector:_selector]; + + // Process arguments + NSUInteger numberOfArguments = _methodSignature.numberOfArguments; + NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2]; + for (NSUInteger i = 2; i < numberOfArguments; i++) { + const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i]; + switch (argumentType[0]) { + +#define RCT_ARG_BLOCK(_logic) \ + [argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \ + _logic \ + [invocation setArgument:&value atIndex:index]; \ + }]; \ + +#define RCT_CASE(_value, _class, _logic) \ + case _value: { \ + RCT_ARG_BLOCK( \ + if (json && ![json isKindOfClass:[_class class]]) { \ + RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \ + json, RCTModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \ + return; \ + } \ + _logic \ + ) \ + break; \ + } + + RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); ); + RCT_CASE('*', NSString, const char *value = [json UTF8String]; ); + +#define RCT_SIMPLE_CASE(_value, _type, _selector) \ + case _value: { \ + RCT_ARG_BLOCK( \ + if (json && ![json respondsToSelector:@selector(_selector)]) { \ + RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \ + index, json, RCTModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \ + return; \ + } \ + _type value = [json _selector]; \ + ) \ + break; \ + } + + RCT_SIMPLE_CASE('c', char, charValue) + RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue) + RCT_SIMPLE_CASE('s', short, shortValue) + RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue) + RCT_SIMPLE_CASE('i', int, intValue) + RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue) + RCT_SIMPLE_CASE('l', long, longValue) + RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue) + RCT_SIMPLE_CASE('q', long long, longLongValue) + RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue) + RCT_SIMPLE_CASE('f', float, floatValue) + RCT_SIMPLE_CASE('d', double, doubleValue) + RCT_SIMPLE_CASE('B', BOOL, boolValue) + + default: { + static const char *blockType = @encode(typeof(^{})); + if (!strcmp(argumentType, blockType)) { + RCT_ARG_BLOCK( + if (json && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, + json, RCTModuleNameForClass(_moduleClass), _JSMethodName); + return; + } + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing id value = (json ? ^(NSArray *args) { + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, args]]; + } : ^(NSArray *unused) {}); + ) + } else { + RCT_ARG_BLOCK( id value = json; ) + } + break; + } + } + } + _argumentBlocks = [argumentBlocks copy]; + } + return self; +} + +- (void)invokeWithBridge:(RCTBridge *)bridge + module:(id)module + arguments:(NSArray *)arguments +{ + +#if DEBUG + + // Sanity check + RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ + %@ on a module of class %@", _methodName, [module class]); +#endif + + // Safety check + if (arguments.count != _argumentBlocks.count) { + RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd", + RCTModuleNameForClass(_moduleClass), _JSMethodName, + arguments.count, _argumentBlocks.count); + return; + } + + // Create invocation (we can't re-use this as it wouldn't be thread-safe) + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature]; + [invocation setArgument:&_selector atIndex:1]; + + // Set arguments + NSUInteger index = 0; + for (id json in arguments) { + id arg = (json == [NSNull null]) ? nil : json; + void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index]; + block(bridge, invocation, index + 2, arg); + index ++; + } + + // Invoke method + [invocation invokeWithTarget:_isClassMethod ? [module class] : module]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName]; +} + +@end + /** * This function parses the exported methods inside RCTBridgeModules and * generates an array of arrays of RCTModuleMethod objects, keyed @@ -193,6 +295,16 @@ 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"); @@ -202,53 +314,23 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) NSArray *classes = RCTBridgeModuleClassesByModuleID(); NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]]; - NSCharacterSet *plusMinusCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"+-"]; for (RCTExportValue addr = section->offset; addr < section->offset + section->size; - addr += sizeof(id) * 2) { + addr += sizeof(const char **) * 2) { - const char **entry = (const char **)(mach_header + addr); - NSScanner *scanner = [NSScanner scannerWithString:@(entry[0])]; + // Get data entry + const char **entries = (const char **)(mach_header + addr); - NSString *plusMinus; - if (![scanner scanCharactersFromSet:plusMinusCharacterSet intoString:&plusMinus]) continue; - if (![scanner scanString:@"[" intoString:NULL]) continue; - - NSString *className; - if (![scanner scanUpToString:@" " intoString:&className]) continue; - [scanner scanString:@" " intoString:NULL]; - - NSString *selectorName; - if (![scanner scanUpToString:@"]" intoString:&selectorName]) continue; - - Class moduleClass = NSClassFromString(className); - if (moduleClass == Nil) continue; - - SEL selector = NSSelectorFromString(selectorName); - Method method = ([plusMinus characterAtIndex:0] == '+' ? class_getClassMethod : class_getInstanceMethod)(moduleClass, selector); - if (method == nil) continue; - - unsigned int argumentCount = method_getNumberOfArguments(method); - NSMutableIndexSet *blockArgumentIndexes = [NSMutableIndexSet indexSet]; - static const char *blockType = @encode(typeof(^{})); - for (unsigned int i = 2; i < argumentCount; i++) { - char *type = method_copyArgumentType(method, i); - if (!strcmp(type, blockType)) { - [blockArgumentIndexes addIndex:i - 2]; - } - free(type); - } - - NSString *JSMethodName = strlen(entry[1]) ? @(entry[1]) : [NSStringFromSelector(selector) componentsSeparatedByString:@":"][0]; + // Create method RCTModuleMethod *moduleMethod = - [[RCTModuleMethod alloc] initWithSelector:selector - JSMethodName:JSMethodName - arity:method_getNumberOfArguments(method) - 2 - blockArgumentIndexes:blockArgumentIndexes]; + [[RCTModuleMethod alloc] initWithMethodName:@(entries[0]) + JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil]; - NSArray *methods = methodsByModuleClassName[className]; - methodsByModuleClassName[className] = methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod]; + // Cache method + NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName]; + methodsByModuleClassName[moduleMethod.moduleClassName] = + methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod]; } methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]]; @@ -256,7 +338,7 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) methodsByModuleID[moduleID] = methodsByModuleClassName[NSStringFromClass(moduleClass)]; }]; }); - + return methodsByModuleID; } @@ -303,19 +385,12 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) @"type": @"remote", }; }]; - + NSDictionary *module = @{ @"moduleID": @(moduleID), @"methods": methodsByName }; - // Add static constants - if (RCTClassOverridesClassMethod(moduleClass, @selector(constantsToExport))) { - NSMutableDictionary *mutableModule = [module mutableCopy]; - mutableModule[@"constants"] = [moduleClass constantsToExport] ?: @{}; - module = [mutableModule copy]; - } - remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module; }]; }); @@ -324,14 +399,15 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) NSMutableDictionary *moduleConfig = [[NSMutableDictionary alloc] init]; [modulesByName enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, id module, BOOL *stop) { - // Add "psuedo-constants" + // Add constants NSMutableDictionary *config = remoteModuleConfigByClassName[NSStringFromClass([module class])]; - if (RCTClassOverridesInstanceMethod([module class], @selector(constantsToExport))) { - NSMutableDictionary *mutableConfig = [NSMutableDictionary dictionaryWithDictionary:config]; - NSMutableDictionary *mutableConstants = [NSMutableDictionary dictionaryWithDictionary:config[@"constants"]]; - [mutableConstants addEntriesFromDictionary:[module constantsToExport]]; - mutableConfig[@"constants"] = mutableConstants; // There's no real need to copy this - config = mutableConfig; // Nor this - receiver is unlikely to mutate it + if ([module respondsToSelector:@selector(constantsToExport)]) { + NSDictionary *constants = [module constantsToExport]; + if (constants) { + NSMutableDictionary *mutableConfig = [NSMutableDictionary dictionaryWithDictionary:config]; + mutableConfig[@"constants"] = constants; // There's no real need to copy this + config = mutableConfig; // Nor this - receiver is unlikely to mutate it + } } moduleConfig[moduleName] = config; @@ -340,6 +416,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) return moduleConfig; } + /** * As above, but for local modules/methods, which represent JS classes * and methods that will be called by the native code via the bridge. @@ -417,21 +494,19 @@ static NSDictionary *RCTLocalModulesConfig() static id _latestJSExecutor; -- (instancetype)initWithJavaScriptExecutor:(id)javaScriptExecutor - moduleProvider:(RCTBridgeModuleProviderBlock)block +- (instancetype)initWithExecutor:(id)executor + moduleProvider:(RCTBridgeModuleProviderBlock)block { if ((self = [super init])) { - _javaScriptExecutor = javaScriptExecutor; + _javaScriptExecutor = executor; _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL); // Register passed-in module instances NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; - if (block) { - for (id module in block(self)) { - preregisteredModules[RCTModuleNameForClass([module class])] = module; - } + for (id module in block ? block() : nil) { + preregisteredModules[RCTModuleNameForClass([module class])] = module; } // Instantiate modules @@ -444,14 +519,14 @@ static id _latestJSExecutor; // Preregistered instances takes precedence, no questions asked if (!preregisteredModules[moduleName]) { // It's OK to have a name collision as long as the second instance is nil - RCTAssert(RCTCreateModuleInstance(moduleClass, self) == nil, + RCTAssert([[moduleClass alloc] init] == nil, @"Attempted to register RCTBridgeModule class %@ for the name '%@', \ but name was already registered by class %@", moduleClass, moduleName, [modulesByName[moduleName] class]); } } else { // Module name hasn't been used before, so go ahead and instantiate - id module = RCTCreateModuleInstance(moduleClass, self); + id module = [[moduleClass alloc] init]; if (module) { _modulesByID[moduleID] = modulesByName[moduleName] = module; } @@ -461,6 +536,13 @@ static id _latestJSExecutor; // Store modules _modulesByName = [modulesByName copy]; + // Set bridge + for (id module in _modulesByName.allValues) { + if ([module respondsToSelector:@selector(setBridge:)]) { + module.bridge = self; + } + } + // Inject module data into JS context NSString *configJSON = RCTJSONStringify(@{ @"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName), @@ -470,12 +552,12 @@ static id _latestJSExecutor; [_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) { dispatch_semaphore_signal(semaphore); }]; - + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) { RCTLogMustFix(@"JavaScriptExecutor took too long to inject JSON object"); } } - + return self; } @@ -501,23 +583,29 @@ static id _latestJSExecutor; - (void)invalidate { + // Release executor if (_latestJSExecutor == _javaScriptExecutor) { _latestJSExecutor = nil; } [_javaScriptExecutor invalidate]; _javaScriptExecutor = nil; - - dispatch_sync(_shadowQueue, ^{ + + // Wait for queued methods to finish + dispatch_sync(self.shadowQueue, ^{ // Make sure all dispatchers have been executed before continuing // TODO: is this still needed? }); - + + // Invalidate modules for (id target in _modulesByID.allObjects) { if ([target respondsToSelector:@selector(invalidate)]) { [(id)target invalidate]; } } - [_modulesByID removeAllObjects]; + + // Release modules (breaks retain cycle if module has strong bridge reference) + _modulesByID = nil; + _modulesByName = nil; } /** @@ -537,10 +625,10 @@ static id _latestJSExecutor; NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod]; RCTAssert(moduleID != nil, @"Module '%@' not registered.", [[moduleDotMethod componentsSeparatedByString:@"."] firstObject]); - + NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod]; RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod); - + [self _invokeAndProcessModule:@"BatchedBridge" method:@"callFunctionReturnFlushedQueue" arguments:@[moduleID, methodID, args ?: @[]]]; @@ -554,12 +642,12 @@ static id _latestJSExecutor; onComplete(scriptLoadError); return; } - + [_javaScriptExecutor executeJSCall:@"BatchedBridge" method:@"flushedQueue" arguments:@[] - callback:^(id objcValue, NSError *error) { - [self _handleBuffer:objcValue]; + callback:^(id json, NSError *error) { + [self _handleBuffer:json]; onComplete(error); }]; }]; @@ -570,35 +658,28 @@ static id _latestJSExecutor; - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args { NSTimeInterval startJS = RCTTGetAbsoluteTime(); - - RCTJavaScriptCallback processResponse = ^(id objcValue, NSError *error) { + + RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { NSTimeInterval startNative = RCTTGetAbsoluteTime(); - [self _handleBuffer:objcValue]; - + [self _handleBuffer:json]; + NSTimeInterval end = RCTTGetAbsoluteTime(); NSTimeInterval timeJS = startNative - startJS; NSTimeInterval timeNative = end - startNative; - + // TODO: surface this performance information somewhere - [[NSNotificationCenter defaultCenter] postNotificationName:@"PERF" object:nil userInfo:@{@"JS": @(timeJS * 1000000), @"Native": @(timeNative * 1000000)}]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"PERF" object:nil userInfo:@{ + @"JS": @(timeJS * 1000000), + @"Native": @(timeNative * 1000000) + }]; }; - + [_javaScriptExecutor executeJSCall:module method:method arguments:args callback:processResponse]; } -/** - * TODO (#5906496): Have responses piggy backed on a round trip with ObjC->JS requests. - */ -- (void)_sendResponseToJavaScriptCallbackID:(NSInteger)cbID args:(NSArray *)args -{ - [self _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[@(cbID), args]]; -} - #pragma mark - Payload Processing - (void)_handleBuffer:(id)buffer @@ -606,12 +687,12 @@ static id _latestJSExecutor; if (buffer == nil || buffer == (id)kCFNull) { return; } - + if (![buffer isKindOfClass:[NSArray class]]) { RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class])); return; } - + NSArray *requestsArray = (NSArray *)buffer; NSUInteger bufferRowCount = [requestsArray count]; NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1; @@ -619,7 +700,7 @@ static id _latestJSExecutor; RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount); return; } - + for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) { id field = [requestsArray objectAtIndex:fieldIndex]; if (![field isKindOfClass:[NSArray class]]) { @@ -627,18 +708,18 @@ static id _latestJSExecutor; return; } } - + NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs]; NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs]; NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss]; - + NSUInteger numRequests = [moduleIDs count]; BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count]; if (!allSame) { RCTLogError(@"Invalid data message - all must be length: %zd", numRequests); return; } - + for (NSUInteger i = 0; i < numRequests; i++) { @autoreleasepool { [self _handleRequestNumber:i @@ -647,9 +728,9 @@ static id _latestJSExecutor; params:paramsArrays[i]]; } } - + // TODO: only used by RCTUIManager - can we eliminate this special case? - dispatch_async(_shadowQueue, ^{ + dispatch_async(self.shadowQueue, ^{ for (id module in _modulesByID.allObjects) { if ([module respondsToSelector:@selector(batchDidComplete)]) { [module batchDidComplete]; @@ -668,170 +749,42 @@ static id _latestJSExecutor; return NO; } + // Look up method NSArray *methods = RCTExportedMethodsByModuleID()[moduleID]; if (methodID >= methods.count) { RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]); return NO; } - RCTModuleMethod *method = methods[methodID]; - NSUInteger methodArity = method.arity; - if (params.count != methodArity) { - RCTLogError(@"Expected %tu arguments but got %tu invoking %@.%@", - methodArity, - params.count, - RCTModuleNamesByID[moduleID], - method.JSMethodName); - return NO; - } - + __weak RCTBridge *weakSelf = self; - dispatch_async(_shadowQueue, ^{ + dispatch_async(self.shadowQueue, ^{ __strong RCTBridge *strongSelf = weakSelf; - + if (!strongSelf.isValid) { // strongSelf has been invalidated since the dispatch_async call and this // invocation should not continue. return; } - - // TODO: we should just store module instances by index, since that's how we look them up anyway - id target = strongSelf->_modulesByID[moduleID]; - RCTAssert(target != nil, @"No module found for name '%@'", RCTModuleNamesByID[moduleID]); - - SEL selector = method.selector; - NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - [invocation setArgument:&target atIndex:0]; - [invocation setArgument:&selector atIndex:1]; - - // Retain used blocks until after invocation completes. - NS_VALID_UNTIL_END_OF_SCOPE NSMutableArray *blocks = [NSMutableArray array]; - - [params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) { - if ([param isEqual:[NSNull null]]) { - param = nil; - } else if ([method.blockArgumentIndexes containsIndex:idx]) { - id block = [strongSelf createResponseSenderBlock:[param integerValue]]; - [blocks addObject:block]; - param = block; - } - - NSUInteger argIdx = idx + 2; - - // TODO: can we do this lookup in advance and cache the logic instead of - // recalculating it every time for every parameter? - BOOL shouldSet = YES; - const char *argumentType = [methodSignature getArgumentTypeAtIndex:argIdx]; - switch (argumentType[0]) { - case ':': - if ([param isKindOfClass:[NSString class]]) { - SEL sel = NSSelectorFromString(param); - [invocation setArgument:&sel atIndex:argIdx]; - shouldSet = NO; - } - break; - - case '*': - if ([param isKindOfClass:[NSString class]]) { - const char *string = [param UTF8String]; - [invocation setArgument:&string atIndex:argIdx]; - shouldSet = NO; - } - break; - - // TODO: it seems like an error if the param doesn't respond - // so we should probably surface that error rather than failing silently -#define CASE(_value, _type, _selector) \ - case _value: \ - if ([param respondsToSelector:@selector(_selector)]) { \ - _type value = [param _selector]; \ - [invocation setArgument:&value atIndex:argIdx]; \ - shouldSet = NO; \ - } \ - break; - - CASE('c', char, charValue) - CASE('C', unsigned char, unsignedCharValue) - CASE('s', short, shortValue) - CASE('S', unsigned short, unsignedShortValue) - CASE('i', int, intValue) - CASE('I', unsigned int, unsignedIntValue) - CASE('l', long, longValue) - CASE('L', unsigned long, unsignedLongValue) - CASE('q', long long, longLongValue) - CASE('Q', unsigned long long, unsignedLongLongValue) - CASE('f', float, floatValue) - CASE('d', double, doubleValue) - CASE('B', BOOL, boolValue) - - default: - break; - } - - if (shouldSet) { - [invocation setArgument:¶m atIndex:argIdx]; - } - }]; - + + // Look up module + id module = strongSelf->_modulesByID[moduleID]; + if (!module) { + RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]); + return; + } + @try { - [invocation invoke]; + [method invokeWithBridge:strongSelf module:module arguments:params]; } @catch (NSException *exception) { - RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception); + RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception); } }); - + return YES; } -/** - * Returns a callback that reports values back to the JS thread. - * TODO (#5906496): These responses should go into their own queue `MessageQueue.m` that - * mirrors the JS queue and protocol. For now, we speak the "language" of the JS - * queue by packing it into an array that matches the wire protocol. - */ -- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)cbID -{ - if (!cbID) { - return nil; - } - - return ^(NSArray *args) { - [self _sendResponseToJavaScriptCallbackID:cbID args:args]; - }; -} - -+ (NSInvocation *)invocationForAdditionalArguments:(NSUInteger)argCount -{ - static NSMutableDictionary *invocations; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - invocations = [NSMutableDictionary dictionary]; - }); - - id key = @(argCount); - NSInvocation *invocation = invocations[key]; - if (invocation == nil) { - NSString *objCTypes = [@"v@:" stringByPaddingToLength:3 + argCount withString:@"@" startingAtIndex:0]; - NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:objCTypes.UTF8String]; - invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - invocations[key] = invocation; - } - - return invocation; -} - -- (void)registerRootView:(RCTRootView *)rootView -{ - // TODO: only used by RCTUIManager - can we eliminate this special case? - for (id module in _modulesByID.allObjects) { - if ([module respondsToSelector:@selector(registerRootView:)]) { - [module registerRootView:rootView]; - } - } -} - + (BOOL)hasValidJSExecutor { return (_latestJSExecutor != nil && [_latestJSExecutor isValid]); @@ -844,7 +797,7 @@ static id _latestJSExecutor; return; } NSMutableArray *args = [NSMutableArray arrayWithObject:level]; - + // TODO (#5906496): Find out and document why we skip the first object for (id ob in [objects subarrayWithRange:(NSRange){1, [objects count] - 1}]) { if ([NSJSONSerialization isValidJSONObject:@[ob]]) { @@ -853,11 +806,12 @@ static id _latestJSExecutor; [args addObject:[ob description]]; } } - // Note the js executor could get invalidated while we're trying to call this...need to watch out for that. + + // Note: the js executor could get invalidated while we're trying to call this...need to watch out for that. [_latestJSExecutor executeJSCall:@"RCTLog" method:@"logIfNoNativeHook" arguments:args - callback:^(id objcValue, NSError *error) {}]; + callback:^(id json, NSError *error) {}]; } @end diff --git a/ReactKit/Base/RCTBridgeModule.h b/ReactKit/Base/RCTBridgeModule.h index d9df70a22..2627a0b9f 100644 --- a/ReactKit/Base/RCTBridgeModule.h +++ b/ReactKit/Base/RCTBridgeModule.h @@ -19,10 +19,12 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); @optional /** - * Optional initializer for modules that require access - * to bridge features, such as sending events or making JS calls + * A reference to the RCTBridge. Useful for modules that require access + * to bridge features, such as sending events or making JS calls. This + * will be set automatically by the bridge when it initializes the module. +* To implement this in your module, just add @synthesize bridge = _bridge; */ -- (instancetype)initWithBridge:(RCTBridge *)bridge; +@property (nonatomic, strong) RCTBridge *bridge; /** * The module name exposed to JS. If omitted, this will be inferred @@ -42,17 +44,11 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); /** * Injects constants into JS. These constants are made accessible via - * NativeModules.moduleName.X. Note that this method is not inherited when you - * subclass a module, and you should not call [super constantsToExport] when - * implementing it. - */ -+ (NSDictionary *)constantsToExport; - -/** - * Some "constants" are not really constant, and need to be re-generated - * each time the bridge module is created. Support for this feature is - * deprecated and may be going away or changing, but for now you can use - * the -constantsToExport instance method to register these "pseudo-constants". + * NativeModules.ModuleName.X. This method is called when the module is + * registered by the bridge. It is only called once for the lifetime of the + * bridge, so it is not suitable for returning dynamic values, but may be + * used for long-lived values such as session keys, that are regenerated only + * as part of a reload of the entire React application. */ - (NSDictionary *)constantsToExport; diff --git a/ReactKit/Base/RCTJavaScriptExecutor.h b/ReactKit/Base/RCTJavaScriptExecutor.h index 7062570a4..4d32f1c2f 100644 --- a/ReactKit/Base/RCTJavaScriptExecutor.h +++ b/ReactKit/Base/RCTJavaScriptExecutor.h @@ -5,7 +5,7 @@ #import "RCTInvalidating.h" typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); -typedef void (^RCTJavaScriptCallback)(id objcValue, NSError *error); +typedef void (^RCTJavaScriptCallback)(id json, NSError *error); /** * Abstracts away a JavaScript execution context - we may be running code in a diff --git a/ReactKit/Base/RCTLog.h b/ReactKit/Base/RCTLog.h index a97d13458..ba72bc8ff 100644 --- a/ReactKit/Base/RCTLog.h +++ b/ReactKit/Base/RCTLog.h @@ -20,28 +20,22 @@ // If defined, only log messages that match this regex will fatal #define RCTLOG_FATAL_REGEX nil -#define _RCTLog(__RCTLog__level, ...) do { \ - NSString *__RCTLog__levelStr; \ - switch(__RCTLog__level) { \ - case RCTLOG_INFO: __RCTLog__levelStr = @"info"; break; \ - case RCTLOG_WARN: __RCTLog__levelStr = @"warn"; break; \ - case RCTLOG_ERROR: __RCTLog__levelStr = @"error"; break; \ - case RCTLOG_MUSTFIX: __RCTLog__levelStr = @"mustfix"; break; \ - } \ - NSString *__RCTLog__msg = _RCTLogObjects(RCTLogFormat(__VA_ARGS__), __RCTLog__levelStr); \ - if (__RCTLog__level >= RCTLOG_FATAL_LEVEL) { \ - BOOL __RCTLog__fail = YES; \ - if (RCTLOG_FATAL_REGEX) { \ - NSError *__RCTLog__e; \ - NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:&__RCTLog__e]; \ - __RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \ - } \ - RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \ - } \ - if (__RCTLog__level >= RCTLOG_REDBOX_LEVEL) { \ - RCTRedBox *__RCTLog__redBox = [RCTRedBox sharedInstance]; \ - [__RCTLog__redBox showErrorMessage:__RCTLog__msg]; \ - } \ +extern __unsafe_unretained NSString *RCTLogLevels[]; + +#define _RCTLog(_level, ...) do { \ + NSString *__RCTLog__levelStr = RCTLogLevels[_level - 1]; \ + NSString *__RCTLog__msg = RCTLogObjects(RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__), __RCTLog__levelStr); \ + if (_level >= RCTLOG_FATAL_LEVEL) { \ + BOOL __RCTLog__fail = YES; \ + if (RCTLOG_FATAL_REGEX) { \ + NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:NULL]; \ + __RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \ + } \ + RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \ + } \ + if (_level >= RCTLOG_REDBOX_LEVEL) { \ + [[RCTRedBox sharedInstance] showErrorMessage:__RCTLog__msg]; \ + } \ } while (0) #define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__) @@ -50,20 +44,15 @@ #define RCTLogError(...) _RCTLog(RCTLOG_ERROR, __VA_ARGS__) #define RCTLogMustFix(...) _RCTLog(RCTLOG_MUSTFIX, __VA_ARGS__) -#define RCTLogFormat(...) _RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__) -#define RCTLogFormatString(...) _RCTLogFormatString(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__) - #ifdef __cplusplus extern "C" { #endif -NSString *_RCTLogObjects(NSArray *objects, NSString *level); -NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5); -NSString *_RCTLogFormatString(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5); +NSString *RCTLogObjects(NSArray *objects, NSString *level); +NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5); + +void RCTInjectLogFunction(void (^logFunction)(NSString *msg)); #ifdef __cplusplus } #endif - -typedef void (^RCTLogFunction)(NSString *format, NSString *str); -void RCTInjectLogFunction(RCTLogFunction func); diff --git a/ReactKit/Base/RCTLog.m b/ReactKit/Base/RCTLog.m index dcf60084e..862eb5a26 100644 --- a/ReactKit/Base/RCTLog.m +++ b/ReactKit/Base/RCTLog.m @@ -4,10 +4,17 @@ #import "RCTBridge.h" -static RCTLogFunction injectedLogFunction; +__unsafe_unretained NSString *RCTLogLevels[] = { + @"info", + @"warn", + @"error", + @"mustfix" +}; -void RCTInjectLogFunction(RCTLogFunction func) { - injectedLogFunction = func; +static void (^RCTInjectedLogFunction)(NSString *msg); + +void RCTInjectLogFunction(void (^logFunction)(NSString *msg)) { + RCTInjectedLogFunction = logFunction; } static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName) @@ -21,7 +28,7 @@ static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const } // TODO (#5906496): // kinda ugly that this is tied to RCTBridge -NSString *_RCTLogObjects(NSArray *objects, NSString *level) +NSString *RCTLogObjects(NSArray *objects, NSString *level) { NSString *str = objects[0]; #if TARGET_IPHONE_SIMULATOR @@ -33,8 +40,8 @@ NSString *_RCTLogObjects(NSArray *objects, NSString *level) { // Print normal errors with timestamps when not in simulator. // Non errors are already compiled out above, so log as error here. - if (injectedLogFunction) { - injectedLogFunction(@">\n %@", str); + if (RCTInjectedLogFunction) { + RCTInjectedLogFunction(str); } else { NSLog(@">\n %@", str); } @@ -42,14 +49,14 @@ NSString *_RCTLogObjects(NSArray *objects, NSString *level) return str; } -// Returns array of objects. First arg is a simple string to print, remaining args are objects to pass through to the debugger so they are -// inspectable in the console. -NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) +// Returns array of objects. First arg is a simple string to print, remaining args +// are objects to pass through to the debugger so they are inspectable in the console. +NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) { va_list args; va_start(args, format); NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName); - + // Pull out NSObjects so we can pass them through as inspectable objects to the js debugger NSArray *formatParts = [format componentsSeparatedByString:@"%"]; NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble]; @@ -77,12 +84,3 @@ NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, N [objectsOut addObjectsFromArray:objects]; return objectsOut; } - -NSString *_RCTLogFormatString(const char *file, int lineNumber, const char *funcName, NSString *format, ...) -{ - va_list args; - va_start (args, format); - NSString *body = [[NSString alloc] initWithFormat:format arguments:args]; - va_end (args); - return [NSString stringWithFormat:@"%@ %@", _RCTLogPreamble(file, lineNumber, funcName), body]; -} diff --git a/ReactKit/Base/RCTRedBox.m b/ReactKit/Base/RCTRedBox.m index 917331f2b..c192598ca 100644 --- a/ReactKit/Base/RCTRedBox.m +++ b/ReactKit/Base/RCTRedBox.m @@ -2,7 +2,6 @@ #import "RCTRedBox.h" -#import "RCTRootView.h" #import "RCTUtils.h" @interface RCTRedBoxWindow : UIWindow @@ -13,31 +12,31 @@ { UIView *_rootView; UITableView *_stackTraceTableView; - + NSString *_lastErrorMessage; NSArray *_lastStackTrace; - + UITableViewCell *_cachedMessageCell; } - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { - + self.windowLevel = UIWindowLevelStatusBar + 5; self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1]; self.hidden = YES; - + UIViewController *rootController = [[UIViewController alloc] init]; self.rootViewController = rootController; _rootView = rootController.view; _rootView.backgroundColor = [UIColor clearColor]; - + const CGFloat buttonHeight = 60; CGRect detailsFrame = _rootView.bounds; detailsFrame.size.height -= buttonHeight; - + _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; _stackTraceTableView.delegate = self; _stackTraceTableView.dataSource = self; @@ -45,21 +44,21 @@ _stackTraceTableView.separatorColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3]; _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; [_rootView addSubview:_stackTraceTableView]; - + UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom]; dismissButton.titleLabel.font = [UIFont systemFontOfSize:14]; [dismissButton setTitle:@"Dismiss (ESC)" forState:UIControlStateNormal]; [dismissButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal]; [dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; [dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside]; - + UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; reloadButton.titleLabel.font = [UIFont systemFontOfSize:14]; [reloadButton setTitle:@"Reload JS (\u2318R)" forState:UIControlStateNormal]; [reloadButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal]; [reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; [reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside]; - + CGFloat buttonWidth = self.bounds.size.width / 2; dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); @@ -87,7 +86,7 @@ { _lastStackTrace = stack; _lastErrorMessage = message; - + if (self.hidden && shouldShow) { _cachedMessageCell = [self reuseCell:nil forErrorMessage:message]; @@ -109,7 +108,7 @@ - (void)reload { - [RCTRootView reloadAll]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil]; [self dismiss]; } @@ -149,9 +148,9 @@ cell.backgroundColor = [UIColor clearColor]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } - + cell.textLabel.text = message; - + return cell; } @@ -168,7 +167,7 @@ cell.selectedBackgroundView = [[UIView alloc] init]; cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2]; } - + cell.textLabel.text = stackFrame[@"methodName"]; cell.detailTextLabel.text = cell.detailTextLabel.text = [NSString stringWithFormat:@"%@:%@", [stackFrame[@"file"] lastPathComponent], stackFrame[@"lineNumber"]]; return cell; @@ -179,7 +178,7 @@ if ([indexPath section] == 0) { NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; - + NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16], NSParagraphStyleAttributeName: paragraphStyle}; CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; @@ -206,14 +205,14 @@ // NOTE: We could use RCTKeyCommands for this, but since // we control this window, we can use the standard, non-hacky // mechanism instead - + return @[ - + // Dismiss red box [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(dismiss)], - + // Reload [UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand @@ -269,12 +268,12 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow { - + #if DEBUG - + dispatch_block_t block = ^{ if (!_window) { - _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; } [_window showErrorMessage:message withStack:stack showIfHidden:shouldShow]; }; @@ -283,9 +282,9 @@ } else { dispatch_async(dispatch_get_main_queue(), block); } - + #endif - + } - (void)dismiss diff --git a/ReactKit/Base/RCTRootView.m b/ReactKit/Base/RCTRootView.m index 703f86daf..b21e53e25 100644 --- a/ReactKit/Base/RCTRootView.m +++ b/ReactKit/Base/RCTRootView.m @@ -14,13 +14,13 @@ #import "RCTWebViewExecutor.h" #import "UIView+ReactKit.h" -NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"; +NSString *const RCTReloadNotification = @"RCTReloadNotification"; @implementation RCTRootView { RCTBridge *_bridge; RCTTouchHandler *_touchHandler; - id _executor; + id _executor; } static Class _globalExecutorClass; @@ -77,7 +77,7 @@ static Class _globalExecutorClass; // Add reload observer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) - name:RCTRootViewReloadNotification + name:RCTReloadNotification object:nil]; } @@ -95,6 +95,10 @@ static Class _globalExecutorClass; [_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer" args:@[self.reactTag]]; + + // TODO: eventually we'll want to be able to share the bridge between + // multiple rootviews, in which case we'll need to move this elsewhere + [_bridge invalidate]; } - (void)bundleFinishedLoading:(NSError *)error @@ -108,7 +112,7 @@ static Class _globalExecutorClass; } } else { - [_bridge registerRootView:self]; + [_bridge.uiManager registerRootView:self]; NSString *moduleName = _moduleName ?: @""; NSDictionary *appParameters = @{ @@ -137,7 +141,7 @@ static Class _globalExecutorClass; // Choose local executor if specified, followed by global, followed by default _executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init]; - _bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor moduleProvider:nil]; + _bridge = [[RCTBridge alloc] initWithExecutor:_executor moduleProvider:nil]; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; [self addGestureRecognizer:_touchHandler]; @@ -229,7 +233,7 @@ static Class _globalExecutorClass; + (void)reloadAll { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewReloadNotification object:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; } - (void)startOrResetInteractionTiming diff --git a/ReactKit/Base/RCTTouchHandler.h b/ReactKit/Base/RCTTouchHandler.h index 53379a78a..46c81b9ef 100644 --- a/ReactKit/Base/RCTTouchHandler.h +++ b/ReactKit/Base/RCTTouchHandler.h @@ -8,7 +8,7 @@ @interface RCTTouchHandler : UIGestureRecognizer -- (instancetype)initWithBridge:(RCTBridge *)bridge; +- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - (void)startOrResetInteractionTiming; - (NSDictionary *)endAndResetInteractionTiming; diff --git a/ReactKit/Base/RCTTouchHandler.m b/ReactKit/Base/RCTTouchHandler.m index 36ab411fe..90e1e64c7 100644 --- a/ReactKit/Base/RCTTouchHandler.m +++ b/ReactKit/Base/RCTTouchHandler.m @@ -67,11 +67,6 @@ NSMutableArray *_bridgeInteractionTiming; } -- (instancetype)initWithTarget:(id)target action:(SEL)action -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (instancetype)initWithBridge:(RCTBridge *)bridge { if ((self = [super initWithTarget:nil action:NULL])) { diff --git a/ReactKit/Base/RCTUtils.h b/ReactKit/Base/RCTUtils.h index 3612b1f27..de203e4ae 100644 --- a/ReactKit/Base/RCTUtils.h +++ b/ReactKit/Base/RCTUtils.h @@ -1,17 +1,15 @@ // Copyright 2004-present Facebook. All Rights Reserved. +#import + #import #import -#import #import "RCTAssert.h" -// Macro to indicate when inherited initializer is not to be used -#define RCT_NOT_DESIGNATED_INITIALIZER() \ -do { \ - RCTAssert(NO, @"%@ is not the designated initializer for instances of %@.", NSStringFromSelector(_cmd), [self class]); \ - return nil; \ -} while (0) +#ifdef __cplusplus +extern "C" { +#endif // Utility functions for JSON object <-> string serialization/deserialization NSString *RCTJSONStringify(id jsonObject, NSError **error); @@ -39,3 +37,10 @@ void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement); // Module subclass support BOOL RCTClassOverridesClassMethod(Class cls, SEL selector); BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector); + +// Enumerate all classes that conform to NSObject protocol +void RCTEnumerateClasses(void (^block)(Class cls)); + +#ifdef __cplusplus +} +#endif diff --git a/ReactKit/Base/RCTUtils.m b/ReactKit/Base/RCTUtils.m index 615c5235e..1b686008f 100644 --- a/ReactKit/Base/RCTUtils.m +++ b/ReactKit/Base/RCTUtils.m @@ -155,6 +155,31 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector) return YES; } } + free(methods); return NO; } +void RCTEnumerateClasses(void (^block)(Class cls)) +{ + static Class *classes; + static unsigned int classCount; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + classes = objc_copyClassList(&classCount); + }); + + for (unsigned int i = 0; i < classCount; i++) + { + Class cls = classes[i]; + Class superclass = cls; + while (superclass) + { + if (class_conformsToProtocol(superclass, @protocol(NSObject))) + { + block(cls); + break; + } + superclass = class_getSuperclass(superclass); + } + } +} diff --git a/ReactKit/Base/RCTViewNodeProtocol.h b/ReactKit/Base/RCTViewNodeProtocol.h index 1fa3e252b..b6f59ea10 100644 --- a/ReactKit/Base/RCTViewNodeProtocol.h +++ b/ReactKit/Base/RCTViewNodeProtocol.h @@ -8,7 +8,7 @@ */ @protocol RCTViewNodeProtocol -@property (nonatomic, strong) NSNumber *reactTag; +@property (nonatomic, copy) NSNumber *reactTag; - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex; - (void)removeReactSubview:(id)subview; @@ -21,6 +21,8 @@ @optional // TODO: Deprecate this +// This method is called after layout has been performed for all views known +// to the RCTViewManager. It is only called on UIViews, not shadow views. - (void)reactBridgeDidFinishTransaction; @end diff --git a/ReactKit/Executors/RCTContextExecutor.m b/ReactKit/Executors/RCTContextExecutor.m index e3eabc16f..ac27a9065 100644 --- a/ReactKit/Executors/RCTContextExecutor.m +++ b/ReactKit/Executors/RCTContextExecutor.m @@ -45,7 +45,7 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, fprintf(stderr, "%s\n", [modifiedString UTF8String]); // don't print timestamps and other junk #else // Print normal errors with timestamps to files when not in simulator. - _RCTLogObjects(@[modifiedString], @"log"); + RCTLogObjects(@[modifiedString], @"log"); #endif JSStringRelease(string); } diff --git a/ReactKit/Modules/RCTAlertManager.m b/ReactKit/Modules/RCTAlertManager.m index ce5cabea6..97ca88f86 100644 --- a/ReactKit/Modules/RCTAlertManager.m +++ b/ReactKit/Modules/RCTAlertManager.m @@ -48,10 +48,10 @@ NSArray *buttons = args[@"buttons"]; if (!title && !message) { - RCTLogMustFix(@"Must specify either an alert title, or message, or both"); + RCTLogError(@"Must specify either an alert title, or message, or both"); return; } else if (buttons.count == 0) { - RCTLogMustFix(@"Must have at least one button."); + RCTLogError(@"Must have at least one button."); return; } @@ -68,7 +68,7 @@ NSInteger index = 0; for (NSDictionary *button in buttons) { if (button.count != 1) { - RCTLogMustFix(@"Button definitions should have exactly one key."); + RCTLogError(@"Button definitions should have exactly one key."); } NSString *buttonKey = [button.allKeys firstObject]; NSString *buttonTitle = [button[buttonKey] description]; diff --git a/ReactKit/Modules/RCTLocationObserver.m b/ReactKit/Modules/RCTLocationObserver.m index 315adc842..6290e1850 100644 --- a/ReactKit/Modules/RCTLocationObserver.m +++ b/ReactKit/Modules/RCTLocationObserver.m @@ -5,6 +5,9 @@ #import #import +#import +#import + #import "RCTAssert.h" #import "RCTBridge.h" #import "RCTEventDispatcher.h" @@ -29,17 +32,17 @@ const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters @implementation RCTLocationObserver { CLLocationManager *_locationManager; - RCTEventDispatcher *_eventDispatcher; NSDictionary *_lastLocationEvent; NSMutableDictionary *_pendingRequests; } +@synthesize bridge = _bridge; + #pragma mark - Lifecycle -- (instancetype)initWithBridge:(RCTBridge *)bridge +- (instancetype)init { - if (self = [super init]) { - _eventDispatcher = bridge.eventDispatcher; + if ((self = [super init])) { _pendingRequests = [[NSMutableDictionary alloc] init]; } return self; @@ -107,7 +110,7 @@ const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters }, @"timestamp": @(CACurrentMediaTime()) }; - [_eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event]; + [_bridge.eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event]; NSArray *pendingRequestsCopy; // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize diff --git a/ReactKit/Modules/RCTStatusBarManager.m b/ReactKit/Modules/RCTStatusBarManager.m index 9a49cdd62..b391dc6bd 100644 --- a/ReactKit/Modules/RCTStatusBarManager.m +++ b/ReactKit/Modules/RCTStatusBarManager.m @@ -49,7 +49,7 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() }); } -+ (NSDictionary *)constantsToExport +- (NSDictionary *)constantsToExport { return @{ @"Style": @{ diff --git a/ReactKit/Modules/RCTTiming.m b/ReactKit/Modules/RCTTiming.m index e833f3d5f..d008178b2 100644 --- a/ReactKit/Modules/RCTTiming.m +++ b/ReactKit/Modules/RCTTiming.m @@ -51,35 +51,35 @@ @implementation RCTTiming { RCTSparseArray *_timers; - RCTBridge *_bridge; id _updateTimer; } +@synthesize bridge = _bridge; + + (NSArray *)JSMethods { return @[@"RCTJSTimers.callTimers"]; } -- (instancetype)initWithBridge:(RCTBridge *)bridge +- (instancetype)init { if ((self = [super init])) { - _bridge = bridge; + _timers = [[RCTSparseArray alloc] init]; - [self startTimers]; - + for (NSString *name in @[UIApplicationWillResignActiveNotification, UIApplicationDidEnterBackgroundNotification, UIApplicationWillTerminateNotification]) { - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(stopTimers) name:name object:nil]; } - + for (NSString *name in @[UIApplicationDidBecomeActiveNotification, UIApplicationWillEnterForegroundNotification]) { - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startTimers) name:name @@ -114,14 +114,14 @@ - (void)startTimers { RCTAssertMainThread(); - - if (![self isValid] || _updateTimer != nil) { + + if (![self isValid] || _updateTimer != nil || _timers.count == 0) { return; } _updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; if (_updateTimer) { - [_updateTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; } else { RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead."); _updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60) @@ -135,7 +135,7 @@ - (void)update { RCTAssertMainThread(); - + NSMutableArray *timersToCall = [[NSMutableArray alloc] init]; for (RCTTimer *timer in _timers.allObjects) { if ([timer updateFoundNeedsJSUpdate]) { @@ -145,7 +145,7 @@ _timers[timer.callbackID] = nil; } } - + // call timers that need to be called if ([timersToCall count] > 0) { [_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]]; @@ -185,6 +185,7 @@ repeats:repeats]; dispatch_async(dispatch_get_main_queue(), ^{ _timers[callbackID] = timer; + [self startTimers]; }); } @@ -195,6 +196,9 @@ if (timerID) { dispatch_async(dispatch_get_main_queue(), ^{ _timers[timerID] = nil; + if (_timers.count == 0) { + [self stopTimers]; + } }); } else { RCTLogWarn(@"Called deleteTimer: with a nil timerID"); diff --git a/ReactKit/Modules/RCTUIManager.h b/ReactKit/Modules/RCTUIManager.h index 87e91118a..701c37f93 100644 --- a/ReactKit/Modules/RCTUIManager.h +++ b/ReactKit/Modules/RCTUIManager.h @@ -2,13 +2,18 @@ #import +#import "RCTBridge.h" #import "RCTBridgeModule.h" #import "RCTInvalidating.h" +#import "RCTViewManager.h" @class RCTRootView; @protocol RCTScrollableProtocol; +/** + * The RCTUIManager is the module responsible for updating the view hierarchy. + */ @interface RCTUIManager : NSObject @property (nonatomic, weak) id mainScrollView; @@ -19,8 +24,33 @@ */ @property (nonatomic, readwrite, weak) id nativeMainScrollDelegate; +/** + * Register a root view with the RCTUIManager. Theoretically, a single manager + * can support multiple root views, however this feature is not currently exposed + * and may eventually be removed. + */ - (void)registerRootView:(RCTRootView *)rootView; +/** + * Schedule a block to be executed on the UI thread. Useful if you need to execute + * view logic after all currently queued view updates have completed. + */ +- (void)addUIBlock:(RCTViewManagerUIBlock)block; + +/** + * The view that is currently first responder, according to the JS context. + */ + (UIView *)JSResponder; @end + +/** + * This category makes the current RCTUIManager instance available via the + * RCTBridge, which is useful for RCTBridgeModules or RCTViewManagers that + * need to access the RCTUIManager. + */ +@interface RCTBridge (RCTUIManager) + +@property (nonatomic, readonly) RCTUIManager *uiManager; + +@end diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index 0b34a820b..951013969 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -32,47 +32,6 @@ static void RCTTraverseViewNodes(id view, react_view_node_b } } -static NSDictionary *RCTViewModuleClasses(void) -{ - static NSMutableDictionary *modules; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - modules = [NSMutableDictionary dictionary]; - - unsigned int classCount; - Class *classes = objc_copyClassList(&classCount); - for (unsigned int i = 0; i < classCount; i++) { - - Class cls = classes[i]; - - if (!class_getSuperclass(cls)) { - // Class has no superclass - it's probably something weird - continue; - } - - if (![cls isSubclassOfClass:[RCTViewManager class]]) { - // Not a view module - continue; - } - - // Get module name - NSString *moduleName = [cls moduleName]; - - // Check module name is unique - id existingClass = modules[moduleName]; - RCTCAssert(existingClass == Nil, @"Attempted to register view module class %@ " - "for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass); - - // Add to module list - modules[moduleName] = cls; - } - - free(classes); - }); - - return modules; -} - @interface RCTAnimation : NSObject @property (nonatomic, readonly) NSTimeInterval duration; @@ -190,48 +149,70 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t @implementation RCTUIManager { + dispatch_queue_t _shadowQueue; + // Root views are only mutated on the shadow queue NSMutableSet *_rootViewTags; NSMutableArray *_pendingUIBlocks; NSLock *_pendingUIBlocksLock; - + // Animation RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only RCTLayoutAnimation *_layoutAnimation; // Main thread only - // Keyed by moduleName + // Keyed by viewName NSMutableDictionary *_defaultShadowViews; // RCT thread only NSMutableDictionary *_defaultViews; // Main thread only NSDictionary *_viewManagers; - + // Keyed by React tag RCTSparseArray *_viewManagerRegistry; // RCT thread only RCTSparseArray *_shadowViewRegistry; // RCT thread only RCTSparseArray *_viewRegistry; // Main thread only - - __weak RCTBridge *_bridge; } -- (instancetype)initWithBridge:(RCTBridge *)bridge +@synthesize bridge =_bridge; + +/** + * This function derives the view name automatically + * from the module name. + */ +static NSString *RCTViewNameForModuleName(NSString *moduleName) +{ + NSString *name = moduleName; + if ([name hasSuffix:@"Manager"]) { + name = [name substringToIndex:name.length - @"Manager".length]; + } + return name; +} + +/** + * This private constructor should only be called when creating + * isolated UIImanager instances for testing. Normal initialization + * is via -init:, which is called automatically by the bridge. + */ +- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue +{ + if ((self = [self init])) { + _shadowQueue = shadowQueue; + _viewManagers = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (instancetype)init { if ((self = [super init])) { - - _bridge = bridge; + _pendingUIBlocksLock = [[NSLock alloc] init]; - - // Instantiate view managers - NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init]; - [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) { - viewManagers[moduleName] = [[moduleClass alloc] initWithEventDispatcher:_bridge.eventDispatcher]; - }]; - _viewManagers = viewManagers; + _defaultShadowViews = [[NSMutableDictionary alloc] init]; _defaultViews = [[NSMutableDictionary alloc] init]; - + _viewManagerRegistry = [[RCTSparseArray alloc] init]; _shadowViewRegistry = [[RCTSparseArray alloc] init]; _viewRegistry = [[RCTSparseArray alloc] init]; - + // Internal resources _pendingUIBlocks = [[NSMutableArray alloc] init]; _rootViewTags = [[NSMutableSet alloc] init]; @@ -239,16 +220,35 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t return self; } -- (instancetype)init -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (void)dealloc { RCTAssert(!self.valid, @"must call -invalidate before -dealloc"); } +- (void)setBridge:(RCTBridge *)bridge +{ + if (_bridge) { + + // Clear previous bridge data + [self invalidate]; + } + if (bridge) { + + _bridge = bridge; + _shadowQueue = _bridge.shadowQueue; + + // Get view managers from bridge + NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init]; + [_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) { + if ([manager isKindOfClass:[RCTViewManager class]]) { + viewManagers[RCTViewNameForModuleName(moduleName)] = manager; + } + }]; + + _viewManagers = [viewManagers copy]; + } +} + - (BOOL)isValid { return _viewRegistry != nil; @@ -260,6 +260,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t _viewRegistry = nil; _shadowViewRegistry = nil; + _bridge = nil; [_pendingUIBlocksLock lock]; _pendingUIBlocks = nil; @@ -269,22 +270,22 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t - (void)registerRootView:(RCTRootView *)rootView; { RCTAssertMainThread(); - + NSNumber *reactTag = rootView.reactTag; UIView *existingView = _viewRegistry[reactTag]; RCTCAssert(existingView == nil || existingView == rootView, @"Expect all root views to have unique tag. Added %@ twice", reactTag); - + // Register view _viewRegistry[reactTag] = rootView; CGRect frame = rootView.frame; - + // Register manager (TODO: should we do this, or leave it nil?) - _viewManagerRegistry[reactTag] = _viewManagers[[RCTViewManager moduleName]]; - + _viewManagerRegistry[reactTag] = _viewManagers[@"View"]; + // Register shadow view - dispatch_async(_bridge.shadowQueue, ^{ - + dispatch_async(_shadowQueue, ^{ + RCTShadowView *shadowView = [[RCTShadowView alloc] init]; shadowView.reactTag = reactTag; shadowView.frame = frame; @@ -315,7 +316,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t - (void)addUIBlock:(RCTViewManagerUIBlock)block { RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread"); - + __weak RCTUIManager *weakViewManager = self; __weak RCTSparseArray *weakViewRegistry = _viewRegistry; dispatch_block_t outerBlock = ^{ @@ -333,6 +334,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView { + RCTAssert(![NSThread isMainThread], @"This should never be executed on main thread."); + NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1]; // This is nuanced. In the JS thread, we create a new update buffer @@ -340,7 +343,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t // these structures in the UI-thread block. `NSMutableArray` is not thread // safe so we rely on the fact that we never mutate it after it's passed to // the main thread. - [rootShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}]; + [rootShadowView collectRootUpdatedFrames:viewsWithNewFrames + parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}]; // Parallel arrays NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; @@ -354,12 +358,19 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t [areNew addObject:@(shadowView.isNewView)]; [parentsAreNew addObject:@(shadowView.superview.isNewView)]; } - + for (RCTShadowView *shadowView in viewsWithNewFrames) { // We have to do this after we build the parentsAreNew array. shadowView.newView = NO; } + NSMutableArray *updateBlocks = [[NSMutableArray alloc] init]; + for (RCTShadowView *shadowView in viewsWithNewFrames) { + RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag]; + RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView]; + if (block) [updateBlocks addObject:block]; + } + // Perform layout (possibly animated) NSNumber *rootViewTag = rootShadowView.reactTag; return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { @@ -373,15 +384,16 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t // Convert the frame so it works with anchorPoint = center. CGPoint position = {CGRectGetMidX(frame), CGRectGetMidY(frame)}; CGRect bounds = {0, 0, frame.size}; - + // Avoid crashes due to nan coords if (isnan(position.x) || isnan(position.y) || isnan(bounds.origin.x) || isnan(bounds.origin.y) || isnan(bounds.size.width) || isnan(bounds.size.height)) { - RCTLogError(@"Invalid layout for (%zd)%@. position: %@. bounds: %@", [view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds)); + RCTLogError(@"Invalid layout for (%@)%@. position: %@. bounds: %@", + [view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds)); continue; } - + void (^completion)(BOOL finished) = ^(BOOL finished) { if (self->_layoutAnimation.callback) { self->_layoutAnimation.callback(@[@(finished)]); @@ -395,14 +407,20 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t [updateAnimation performAnimations:^{ view.layer.position = position; view.layer.bounds = bounds; + for (RCTViewManagerUIBlock block in updateBlocks) { + block(self, _viewRegistry); + } } withCompletionBlock:completion]; } else { view.layer.position = position; view.layer.bounds = bounds; + for (RCTViewManagerUIBlock block in updateBlocks) { + block(self, _viewRegistry); + } completion(YES); } - - // Animate view creations + + // Animate view creation BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue]; RCTAnimation *createAnimation = _layoutAnimation.createAnimation; if (shouldAnimateCreation && createAnimation) { @@ -417,12 +435,22 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t } else if ([createAnimation.property isEqual:@"opacity"]) { view.layer.opacity = 1.0; } else { - RCTLogError(@"Unsupported layout animation createConfig property %@", createAnimation.property); + RCTLogError(@"Unsupported layout animation createConfig property %@", + createAnimation.property); + } + for (RCTViewManagerUIBlock block in updateBlocks) { + block(self, _viewRegistry); } } withCompletionBlock:nil]; } } + /** + * Enumerate all active (attached to a parent) views and call + * reactBridgeDidFinishTransaction on them if they implement it. + * TODO: this is quite inefficient. If this was handled via the + * ViewManager instead, it could be done more efficiently. + */ RCTRootView *rootView = _viewRegistry[rootViewTag]; RCTTraverseViewNodes(rootView, ^(id view) { if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { @@ -436,7 +464,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t { NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1]; [topView collectUpdatedProperties:applierBlocks parentProperties:@{}]; - + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { for (RCTApplierBlock block in applierBlocks) { block(viewRegistry); @@ -454,7 +482,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t id container = _viewRegistry[containerID]; RCTAssert(container != nil, @"container view (for ID %@) not found", containerID); - + NSUInteger subviewsCount = [[container reactSubviews] count]; NSMutableArray *indices = [[NSMutableArray alloc] initWithCapacity:subviewsCount]; for (NSInteger childIndex = 0; childIndex < subviewsCount; childIndex++) { @@ -616,11 +644,11 @@ static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, { // TODO: cache respondsToSelector tests if ([manager respondsToSelector:setter]) { - + if (value == [NSNull null]) { value = nil; } - + ((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView); return YES; } @@ -631,9 +659,9 @@ static void RCTSetViewProps(NSDictionary *props, UIView *view, UIView *defaultView, RCTViewManager *manager) { [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { - + SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]); - + // For regular views we don't attempt to set properties // unless the view property has been explicitly exported. RCTCallPropertySetter(setter, obj, view, defaultView, manager); @@ -644,13 +672,13 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView RCTShadowView *defaultView, RCTViewManager *manager) { [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { - + SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]); - + // For shadow views we call any custom setter methods by default, // but if none is specified, we attempt to set property anyway. if (!RCTCallPropertySetter(setter, obj, shadowView, defaultView, manager)) { - + if (obj == [NSNull null]) { // Copy property from default view to current // Note: not just doing `[defaultView valueForKey:key]`, the @@ -661,20 +689,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView } } }]; - + // Update layout [shadowView updateShadowViewLayout]; } - (void)createAndRegisterViewWithReactTag:(NSNumber *)reactTag - moduleName:(NSString *)moduleName + viewName:(NSString *)viewName props:(NSDictionary *)props { RCT_EXPORT(createView); - RCTViewManager *manager = _viewManagers[moduleName]; + RCTViewManager *manager = _viewManagers[viewName]; if (manager == nil) { - RCTLogWarn(@"No manager class found for view with module name \"%@\"", moduleName); + RCTLogWarn(@"No manager class found for view with module name \"%@\"", viewName); manager = [[RCTViewManager alloc] init]; } @@ -682,57 +710,57 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView _viewManagerRegistry[reactTag] = manager; // Generate default view, used for resetting default props - if (!_defaultShadowViews[moduleName]) { - _defaultShadowViews[moduleName] = [manager shadowView]; + if (!_defaultShadowViews[viewName]) { + _defaultShadowViews[viewName] = [manager shadowView]; } - + RCTShadowView *shadowView = [manager shadowView]; - shadowView.moduleName = moduleName; + shadowView.moduleName = viewName; shadowView.reactTag = reactTag; - RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], manager); + RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager); _shadowViewRegistry[shadowView.reactTag] = shadowView; - + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ RCTCAssertMainThread(); // Generate default view, used for resetting default props - if (!uiManager->_defaultViews[moduleName]) { + if (!uiManager->_defaultViews[viewName]) { // Note the default is setup after the props are read for the first time ever // for this className - this is ok because we only use the default for restoring // defaults, which never happens on first creation. - uiManager->_defaultViews[moduleName] = [manager view]; + uiManager->_defaultViews[viewName] = [manager view]; } - + UIView *view = [manager view]; if (view) { - + // Set required properties view.reactTag = reactTag; view.multipleTouchEnabled = YES; view.userInteractionEnabled = YES; // required for touch handling view.layer.allowsGroupOpacity = YES; // required for touch handling - + // Set custom properties - RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], manager); + RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager); } viewRegistry[view.reactTag] = view; }]; } -// TODO: remove moduleName param as it isn't needed -- (void)updateView:(NSNumber *)reactTag moduleName:(__unused NSString *)_ props:(NSDictionary *)props +// TODO: remove viewName param as it isn't needed +- (void)updateView:(NSNumber *)reactTag viewName:(__unused NSString *)_ props:(NSDictionary *)props { RCT_EXPORT(); RCTViewManager *viewManager = _viewManagerRegistry[reactTag]; - NSString *moduleName = [[viewManager class] moduleName]; - + NSString *viewName = RCTViewNameForModuleName([[viewManager class] moduleName]); + RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; - RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], viewManager); - + RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager); + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { UIView *view = uiManager->_viewRegistry[reactTag]; - RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], viewManager); + RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager); }]; } @@ -760,16 +788,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView - (void)batchDidComplete { - // First copy the previous blocks into a temporary variable, then reset the - // pending blocks to a new array. This guards against mutation while - // processing the pending blocks in another thread. + // Gather blocks to be executed now that all view hierarchy manipulations have + // been completed (note that these may still take place before layout has finished) for (RCTViewManager *manager in _viewManagers.allValues) { RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry]; if (uiBlock) { [self addUIBlock:uiBlock]; } } - + // Set up next layout animation if (_nextLayoutAnimation) { RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation; @@ -777,14 +804,14 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView uiManager->_layoutAnimation = layoutAnimation; }]; } - + // Perform layout for (NSNumber *reactTag in _rootViewTags) { RCTShadowView *rootView = _shadowViewRegistry[reactTag]; [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]]; [self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView]; } - + // Clear layout animations if (_nextLayoutAnimation) { [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { @@ -792,12 +819,16 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }]; _nextLayoutAnimation = nil; } - + + // First copy the previous blocks into a temporary variable, then reset the + // pending blocks to a new array. This guards against mutation while + // processing the pending blocks in another thread. [_pendingUIBlocksLock lock]; NSArray *previousPendingUIBlocks = _pendingUIBlocks; _pendingUIBlocks = [[NSMutableArray alloc] init]; [_pendingUIBlocksLock unlock]; - + + // Execute the previously queued UI blocks dispatch_async(dispatch_get_main_queue(), ^{ for (dispatch_block_t block in previousPendingUIBlocks) { block(); @@ -828,7 +859,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView } RCTCAssert([rootView isReactRootView], @"React view not inside RCTRootView"); - + // By convention, all coordinates, whether they be touch coordinates, or // measurement coordinates are with respect to the root view. CGPoint pagePoint = [view.superview convertPoint:frame.origin toView:rootView]; @@ -844,37 +875,6 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }]; } -- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag - errorCallback:(RCTResponseSenderBlock)errorCallback - callback:(RCTResponseSenderBlock)callback -{ - RCT_EXPORT(); - - if (!callback || !errorCallback) { - RCTLogError(@"Callback not provided for navigation scheduling."); - return; - } - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ - if (reactTag) { - //TODO: This is nasty - why is RCTNavigator hard-coded? - id rkObject = viewRegistry[reactTag]; - if ([rkObject isKindOfClass:[RCTNavigator class]]) { - RCTNavigator *navigator = (RCTNavigator *)rkObject; - BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation]; - callback(@[@(wasAcquired)]); - } else { - NSString *msg = - [NSString stringWithFormat: @"Cannot set lock: Tag %@ is not an RCTNavigator", reactTag]; - errorCallback(@[RCTAPIErrorObject(msg)]); - } - } else { - NSString *msg = [NSString stringWithFormat: @"Tag not specified for requestSchedulingJavaScriptNavigation"]; - errorCallback(@[RCTAPIErrorObject(msg)]); - } - }]; -} - - /** * TODO: This could be modified to accept any `RCTViewNodeProtocol`, if * appropriate changes were made to that protocol to support `superview` @@ -884,22 +884,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView + (void)measureLayoutOnNodes:(RCTShadowView *)view ancestor:(RCTShadowView *)ancestor errorCallback:(RCTResponseSenderBlock)errorCallback - callback:(RCTResponseSenderBlock)callback + callback:(__unused RCTResponseSenderBlock)callback { if (!view) { - NSString *msg = [NSString stringWithFormat: @"Attempting to measure view that does not exist %@", view]; - errorCallback(@[RCTAPIErrorObject(msg)]); + RCTLogError(@"Attempting to measure view that does not exist"); return; } if (!ancestor) { - NSString *msg = [NSString stringWithFormat: @"Attempting to measure relative to ancestor that does not exist %@", ancestor]; - errorCallback(@[RCTAPIErrorObject(msg)]); + RCTLogError(@"Attempting to measure relative to ancestor that does not exist"); return; } CGRect result = [RCTShadowView measureLayout:view relativeTo:ancestor]; if (CGRectIsNull(result)) { - NSString *msg = [NSString stringWithFormat: @"view %@ is not an decendant of %@", view, ancestor]; - errorCallback(@[RCTAPIErrorObject(msg)]); + RCTLogError(@"view %@ (tag #%@) is not a decendant of %@ (tag #%@)", + view, view.reactTag, ancestor, ancestor.reactTag); return; } CGFloat leftOffset = result.origin.x; @@ -907,7 +905,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView CGFloat width = result.size.width; CGFloat height = result.size.height; if (isnan(leftOffset) || isnan(topOffset) || isnan(width) || isnan(height)) { - errorCallback(@[RCTAPIErrorObject(@"Attempted to measure layout but offset or dimensions were NaN")]); + RCTLogError(@"Attempted to measure layout but offset or dimensions were NaN"); return; } callback(@[@(topOffset), @(leftOffset), @(width), @(height)]); @@ -965,8 +963,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; if (!shadowView) { - NSString *msg = [NSString stringWithFormat: @"Attempting to measure view that does not exist %@", shadowView]; - errorCallback(@[RCTAPIErrorObject(msg)]); + RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag); return; } NSArray *childShadowViews = [shadowView reactSubviews]; @@ -977,8 +974,8 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView RCTShadowView *childShadowView = [childShadowViews objectAtIndex:ii]; CGRect childLayout = [RCTShadowView measureLayout:childShadowView relativeTo:shadowView]; if (CGRectIsNull(childLayout)) { - NSString *msg = [NSString stringWithFormat: @"view %@ is not a decendant of %@", childShadowView, shadowView]; - errorCallback(@[RCTAPIErrorObject(msg)]); + RCTLogError(@"View %@ (tag #%@) is not a decendant of %@ (tag #%@)", + childShadowView, childShadowView.reactTag, shadowView, shadowView.reactTag); return; } @@ -1088,7 +1085,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _jsResponder = viewRegistry[reactTag]; if (!_jsResponder) { - RCTLogMustFix(@"Invalid view set to be the JS responder - tag %zd", reactTag); + RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag); } }]; } @@ -1102,7 +1099,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }]; } -+ (NSDictionary *)allBubblingEventTypesConfigs +// TODO: these event types should be distributed among the modules +// that declare them. Also, events should be registerable by any class +// that can call event handlers, not just UIViewManagers. This code +// also seems highly redundant - every event has the same properties. +- (NSDictionary *)customBubblingEventTypes { NSMutableDictionary *customBubblingEventTypesConfigs = [@{ // Bubble dispatched events @@ -1192,11 +1193,12 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }, } mutableCopy]; - [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) { + [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) { if (RCTClassOverridesClassMethod(cls, @selector(customBubblingEventTypes))) { NSDictionary *eventTypes = [cls customBubblingEventTypes]; for (NSString *eventName in eventTypes) { - RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event '%@' registered multiple times.", eventName); + RCTCAssert(!customBubblingEventTypesConfigs[eventName], + @"Event '%@' registered multiple times.", eventName); } [customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes]; } @@ -1205,7 +1207,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView return customBubblingEventTypesConfigs; } -+ (NSDictionary *)allDirectEventTypesConfigs +- (NSDictionary *)customDirectEventTypes { NSMutableDictionary *customDirectEventTypes = [@{ @"topScrollBeginDrag": @{ @@ -1243,7 +1245,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }, } mutableCopy]; - [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) { + [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) { if (RCTClassOverridesClassMethod(cls, @selector(customDirectEventTypes))) { NSDictionary *eventTypes = [cls customDirectEventTypes]; for (NSString *eventName in eventTypes) { @@ -1256,11 +1258,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView return customDirectEventTypes; } -+ (NSDictionary *)constantsToExport +- (NSDictionary *)constantsToExport { NSMutableDictionary *allJSConstants = [@{ - @"customBubblingEventTypes": [self allBubblingEventTypesConfigs], - @"customDirectEventTypes": [self allDirectEventTypesConfigs], + @"customBubblingEventTypes": [self customBubblingEventTypes], + @"customDirectEventTypes": [self customDirectEventTypes], @"NSTextAlignment": @{ @"Left": @(NSTextAlignmentLeft), @"Center": @(NSTextAlignmentCenter), @@ -1320,15 +1322,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView }, } mutableCopy]; - [RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) { + [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) { // TODO: should these be inherited? NSDictionary *constants = RCTClassOverridesClassMethod(cls, @selector(constantsToExport)) ? [cls constantsToExport] : nil; if ([constants count]) { - NSMutableDictionary *namespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]]; - RCTAssert(namespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name); + NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]]; + RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name); // add an additional 'Constants' namespace for each class - namespace[@"Constants"] = constants; - allJSConstants[name] = [namespace copy]; + constantsNamespace[@"Constants"] = constants; + allJSConstants[name] = [constantsNamespace copy]; } }]; @@ -1342,10 +1344,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView RCT_EXPORT(); if (_nextLayoutAnimation) { - RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation, config); + RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", + _nextLayoutAnimation, config); } if (config[@"delete"] != nil) { - RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config); + RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config); } _nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config callback:callback]; } @@ -1389,3 +1392,12 @@ static UIView *_jsResponder; } @end + +@implementation RCTBridge (RCTUIManager) + +- (RCTUIManager *)uiManager +{ + return self.modules[NSStringFromClass([RCTUIManager class])]; +} + +@end diff --git a/ReactKit/Views/RCTNavigator.h b/ReactKit/Views/RCTNavigator.h index 5d928efa7..c9051c753 100644 --- a/ReactKit/Views/RCTNavigator.h +++ b/ReactKit/Views/RCTNavigator.h @@ -11,7 +11,7 @@ @property (nonatomic, strong) UIView *reactNavSuperviewLink; @property (nonatomic, assign) NSInteger requestedTopOfStack; -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher; +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; /** * Schedules a JavaScript navigation and prevents `UIKit` from navigating until diff --git a/ReactKit/Views/RCTNavigator.m b/ReactKit/Views/RCTNavigator.m index 74e1621c2..be78fccd6 100644 --- a/ReactKit/Views/RCTNavigator.m +++ b/ReactKit/Views/RCTNavigator.m @@ -126,21 +126,6 @@ NSInteger kNeverProgressed = -10000; */ @implementation RCTNavigationController -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - -- (instancetype)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - -- (instancetype)initWithRootViewController:(UIViewController *)rootViewController -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - /** * @param callback Callback that is invoked when a "scroll" interaction begins * so that `RCTNavigator` can notify `JavaScript`. @@ -275,11 +260,6 @@ NSInteger kNeverProgressed = -10000; @implementation RCTNavigator -- (instancetype)initWithFrame:(CGRect)frame -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:CGRectZero])) { @@ -308,7 +288,6 @@ NSInteger kNeverProgressed = -10000; [self addSubview:_navigationController.view]; [_navigationController.view addSubview:_dummyView]; } - return self; } @@ -363,8 +342,8 @@ NSInteger kNeverProgressed = -10000; (RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextToViewControllerKey]; NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem]; NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem]; - CGFloat destination = indexOfFrom < indexOfTo ? 1.0f : -1.0f; - _dummyView.frame = CGRectMake(destination, 0.0f, 0.0f, 0.0f); + CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0; + _dummyView.frame = (CGRect){destination}; _currentlyTransitioningFrom = indexOfFrom; _currentlyTransitioningTo = indexOfTo; if (indexOfFrom != indexOfTo) { @@ -375,7 +354,7 @@ NSInteger kNeverProgressed = -10000; [weakSelf freeLock]; _currentlyTransitioningFrom = 0; _currentlyTransitioningTo = 0; - _dummyView.frame = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f); + _dummyView.frame = CGRectZero; _displayLink.paused = YES; // Reset the parallel position tracker }]; @@ -462,7 +441,7 @@ NSInteger kNeverProgressed = -10000; } /** - * Must be overridden because UIKit destroys the views superview link when used + * Must be overridden because UIKit removes the view's superview when used * as a navigator - it's considered outside the view hierarchy. */ - (UIView *)reactSuperview @@ -490,7 +469,7 @@ NSInteger kNeverProgressed = -10000; // we can't hook up the VC hierarchy in 'init' because the subviews aren't hooked up yet, // so we do it on demand here [self addControllerToClosestParent:_navigationController]; - + NSInteger viewControllerCount = _navigationController.viewControllers.count; // The "react count" is the count of views that are visible on the navigation // stack. There may be more beyond this - that aren't visible, and may be @@ -563,7 +542,6 @@ NSInteger kNeverProgressed = -10000; _previousRequestedTopOfStack = _requestedTopOfStack; } - // TODO: This will likely fail when performing multiple pushes/pops. We must // free the lock only after the *last* push/pop. - (void)wrapperViewController:(RCTWrapperViewController *)wrapperViewController @@ -574,7 +552,7 @@ didMoveToNavigationController:(UINavigationController *)navigationController // while a push/pop is in progress. return; } - + RCTAssert( (navigationController == nil || [_navigationController.viewControllers containsObject:wrapperViewController]), @"if navigation controller is not nil, it should container the wrapper view controller" diff --git a/ReactKit/Views/RCTNavigatorManager.m b/ReactKit/Views/RCTNavigatorManager.m index 973d4958d..578b42bd0 100644 --- a/ReactKit/Views/RCTNavigatorManager.m +++ b/ReactKit/Views/RCTNavigatorManager.m @@ -5,6 +5,8 @@ #import "RCTConvert.h" #import "RCTNavigator.h" #import "RCTShadowView.h" +#import "RCTSparseArray.h" +#import "RCTUIManager.h" @implementation RCTNavigatorManager @@ -24,5 +26,26 @@ RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack) }; } -@end +// TODO: remove error callbacks +- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag + errorCallback:(RCTResponseSenderBlock)errorCallback + callback:(__unused RCTResponseSenderBlock)callback +{ + RCT_EXPORT(); + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ + if (reactTag) { + RCTNavigator *navigator = viewRegistry[reactTag]; + if ([navigator isKindOfClass:[RCTNavigator class]]) { + BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation]; + callback(@[@(wasAcquired)]); + } else { + RCTLogError(@"Cannot set lock: %@ (tag #%@) is not an RCTNavigator", navigator, reactTag); + } + } else { + RCTLogError(@"Tag not specified for requestSchedulingJavaScriptNavigation"); + } + }]; +} + +@end diff --git a/ReactKit/Views/RCTScrollView.h b/ReactKit/Views/RCTScrollView.h index 82667b205..204ddf494 100644 --- a/ReactKit/Views/RCTScrollView.h +++ b/ReactKit/Views/RCTScrollView.h @@ -12,6 +12,8 @@ @interface RCTScrollView : RCTView +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + /** * If the `contentSize` is not provided, then the `contentSize` will * automatically be determined by the size of the `RKScrollView` subview. @@ -32,6 +34,4 @@ @property (nonatomic, assign) BOOL centerContent; @property (nonatomic, copy) NSArray *stickyHeaderIndices; -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher; - @end diff --git a/ReactKit/Views/RCTScrollView.m b/ReactKit/Views/RCTScrollView.m index d017c7edc..bd03d45de 100644 --- a/ReactKit/Views/RCTScrollView.m +++ b/ReactKit/Views/RCTScrollView.m @@ -31,8 +31,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; - (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { + if ((self = [super initWithFrame:frame])) { [self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)]; } return self; @@ -257,11 +256,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; @synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate; -- (instancetype)initWithFrame:(CGRect)frame -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:CGRectZero])) { diff --git a/ReactKit/Views/RCTScrollViewManager.m b/ReactKit/Views/RCTScrollViewManager.m index 5100d1186..afcf6436a 100644 --- a/ReactKit/Views/RCTScrollViewManager.m +++ b/ReactKit/Views/RCTScrollViewManager.m @@ -31,23 +31,22 @@ RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator) RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator) RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices); RCT_EXPORT_VIEW_PROPERTY(throttleScrollCallbackMS); -RCT_EXPORT_VIEW_PROPERTY(zoomScale); +RCT_EXPORT_VIEW_PROPERTY(zoomScale); // TODO: this needs to be set first because it resets other props like contentOffset RCT_EXPORT_VIEW_PROPERTY(contentInset); RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets); RCT_EXPORT_VIEW_PROPERTY(contentOffset); -+ (NSDictionary *)constantsToExport +- (NSDictionary *)constantsToExport { - return - @{ + return @{ @"DecelerationRate": @{ - @"Normal": @(UIScrollViewDecelerationRateNormal), - @"Fast": @(UIScrollViewDecelerationRateFast), + @"Normal": @(UIScrollViewDecelerationRateNormal), + @"Fast": @(UIScrollViewDecelerationRateFast), }, @"KeyboardDismissMode": @{ - @"None": @(UIScrollViewKeyboardDismissModeNone), - @"Interactive": @(UIScrollViewKeyboardDismissModeInteractive), - @"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag), + @"None": @(UIScrollViewKeyboardDismissModeNone), + @"Interactive": @(UIScrollViewKeyboardDismissModeInteractive), + @"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag), }, }; } diff --git a/ReactKit/Views/RCTShadowView.h b/ReactKit/Views/RCTShadowView.h index ec2b8c353..9a09bad4f 100644 --- a/ReactKit/Views/RCTShadowView.h +++ b/ReactKit/Views/RCTShadowView.h @@ -3,29 +3,15 @@ #import #import "Layout.h" +#import "RCTUIManager.h" #import "RCTViewNodeProtocol.h" @class RCTSparseArray; -// TODO: amalgamate these enums? -typedef NS_ENUM(NSUInteger, RCTLayoutLifecycle) { - RCTLayoutLifecycleUninitialized = 0, - RCTLayoutLifecycleComputed, - RCTLayoutLifecycleDirtied, -}; - -// TODO: is this still needed? -typedef NS_ENUM(NSUInteger, RCTPropagationLifecycle) { - RCTPropagationLifecycleUninitialized = 0, - RCTPropagationLifecycleComputed, - RCTPropagationLifecycleDirtied, -}; - -// TODO: move this to text node? -typedef NS_ENUM(NSUInteger, RCTTextLifecycle) { - RCTTextLifecycleUninitialized = 0, - RCTTextLifecycleComputed, - RCTTextLifecycleDirtied, +typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) { + RCTUpdateLifecycleUninitialized = 0, + RCTUpdateLifecycleComputed, + RCTUpdateLifecycleDirtied, }; // TODO: is this redundact now? @@ -48,7 +34,7 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *); @property (nonatomic, copy) NSString *moduleName; @property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propogate to children @property (nonatomic, strong) UIColor *backgroundColor; // Used to propogate to children -@property (nonatomic, assign) RCTLayoutLifecycle layoutLifecycle; +@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle; /** * isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is @@ -122,11 +108,23 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *); @property (nonatomic, assign) css_wrap_type_t flexWrap; @property (nonatomic, assign) CGFloat flex; -- (void)collectUpdatedProperties:(NSMutableSet *)viewsWithNewProperties parentProperties:(NSDictionary *)parentProperties; -- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint; -- (void)fillCSSNode:(css_node_t *)node; +/** + * Calculate property changes that need to be propagated to the view. + * The applierBlocks set contains RCTApplierBlock functions that must be applied + * on the main thread in order to update the view. + */ +- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties; -// The following are implementation details exposed to subclasses. Do not call them directly +/** + * Calculate all views whose frame needs updating after layout has been calculated. + * The viewsWithNewFrame set contains the reactTags of the views that need updating. + */ +- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint; + +/** + * The following are implementation details exposed to subclasses. Do not call them directly + */ +- (void)fillCSSNode:(css_node_t *)node; - (void)dirtyLayout; - (BOOL)isLayoutDirty; diff --git a/ReactKit/Views/RCTShadowView.m b/ReactKit/Views/RCTShadowView.m index 4877b0d67..f8c32763f 100644 --- a/ReactKit/Views/RCTShadowView.m +++ b/ReactKit/Views/RCTShadowView.m @@ -10,46 +10,6 @@ typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value); typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf); -@interface RCTLayoutAction : NSObject - -@property (nonatomic, readwrite, copy) RCTActionBlock block; -@property (nonatomic, readwrite, copy) RCTResetActionBlock resetBlock; -@property (nonatomic, readwrite, assign) NSInteger precedence; - -@end - -@implementation RCTLayoutAction @end - -#define ACTION_FOR_KEY_DEFAULT(name, default, blockIn) \ -do { \ - RCTLayoutAction *action = [[RCTLayoutAction alloc] init]; \ - action.block = blockIn; \ - action.resetBlock = ^(id idSelf1) { \ - blockIn(idSelf1, default); \ - }; \ - actions[@"" #name ""] = action; \ -} while(0) - -#define ACTION_FOR_KEY(name, blockIn) \ -ACTION_FOR_KEY_DEFAULT(name, @([defaultShadowView name]), (blockIn)) - -#define ACTION_FOR_FLOAT_KEY_DEFAULT(name, default, blockIn) \ -ACTION_FOR_KEY_DEFAULT(name, @(default), ^(id idSelf2, NSNumber *n) { \ - if (isnan([n floatValue])) { \ - RCTLogWarn(@"Got NaN for `"#name"` prop, ignoring"); \ - return; \ - } \ - blockIn(idSelf2, RCTNumberToFloat(n)); \ -}); - -#define ACTION_FOR_FLOAT_KEY(name, blockIn) \ -ACTION_FOR_FLOAT_KEY_DEFAULT(name, [defaultShadowView name], (blockIn)) - -#define ACTION_FOR_DEFAULT_UNDEFINED_KEY(name, blockIn) \ -ACTION_FOR_KEY_DEFAULT(name, nil, ^(id idSelf2, NSNumber *n) { \ - blockIn(idSelf2, n == nil ? CSS_UNDEFINED : [n floatValue]); \ -}); - #define MAX_TREE_DEPTH 30 const NSString *const RCTBackgroundColorProp = @"backgroundColor"; @@ -65,22 +25,16 @@ typedef enum { META_PROP_COUNT, } meta_prop_t; -@interface RCTShadowView() -{ - float _paddingMetaProps[META_PROP_COUNT]; - float _marginMetaProps[META_PROP_COUNT]; -} - -@end - @implementation RCTShadowView { - RCTPropagationLifecycle _propagationLifecycle; - RCTTextLifecycle _textLifecycle; + RCTUpdateLifecycle _propagationLifecycle; + RCTUpdateLifecycle _textLifecycle; NSDictionary *_lastParentProperties; NSMutableArray *_reactSubviews; BOOL _recomputePadding; BOOL _recomputeMargin; + float _paddingMetaProps[META_PROP_COUNT]; + float _marginMetaProps[META_PROP_COUNT]; } @synthesize reactTag = _reactTag; @@ -131,11 +85,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st node->children_count = (int)_reactSubviews.count; } -- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame -{ - [self _applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero]; -} - // The absolute stuff is so that we can take into account our absolute position when rounding in order to // snap to the pixel grid. For example, say you have the following structure: // @@ -165,24 +114,24 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st // width = 213.5 - 106.5 = 107 // You'll notice that this is the same width we calculated for the parent view because we've taken its position into account. -- (void)_applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition +- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition { if (!node->layout.should_update) { return; } node->layout.should_update = false; - _layoutLifecycle = RCTLayoutLifecycleComputed; + _layoutLifecycle = RCTUpdateLifecycleComputed; CGPoint absoluteTopLeft = { RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT]), RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP]) }; - + CGPoint absoluteBottomRight = { RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH]), RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT]) }; - + CGRect frame = { RCTRoundPixelValue(node->layout.position[CSS_LEFT]), RCTRoundPixelValue(node->layout.position[CSS_TOP]), @@ -205,7 +154,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st for (int i = 0; i < node->children_count; ++i) { RCTShadowView *child = (RCTShadowView *)_reactSubviews[i]; - [child _applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; + [child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; } } @@ -238,10 +187,10 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st - (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties { - if (_propagationLifecycle == RCTPropagationLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) { + if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) { return; } - _propagationLifecycle = RCTPropagationLifecycleComputed; + _propagationLifecycle = RCTUpdateLifecycleComputed; _lastParentProperties = parentProperties; NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties]; for (RCTShadowView *child in _reactSubviews) { @@ -253,7 +202,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st { [self fillCSSNode:_cssNode]; layoutNode(_cssNode, CSS_UNDEFINED); - [self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame]; + [self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero]; } + (CGRect)measureLayout:(RCTShadowView *)shadowView relativeTo:(RCTShadowView *)ancestor @@ -277,7 +226,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st - (instancetype)init { if ((self = [super init])) { - + _frame = CGRectMake(0, 0, CSS_UNDEFINED, CSS_UNDEFINED); for (int ii = 0; ii < META_PROP_COUNT; ii++) { @@ -286,9 +235,9 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st } _newView = YES; - _layoutLifecycle = RCTLayoutLifecycleUninitialized; - _propagationLifecycle = RCTPropagationLifecycleUninitialized; - _textLifecycle = RCTTextLifecycleUninitialized; + _layoutLifecycle = RCTUpdateLifecycleUninitialized; + _propagationLifecycle = RCTUpdateLifecycleUninitialized; + _textLifecycle = RCTUpdateLifecycleUninitialized; _reactSubviews = [NSMutableArray array]; @@ -309,46 +258,46 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st - (void)dirtyLayout { - if (_layoutLifecycle != RCTLayoutLifecycleDirtied) { - _layoutLifecycle = RCTLayoutLifecycleDirtied; + if (_layoutLifecycle != RCTUpdateLifecycleDirtied) { + _layoutLifecycle = RCTUpdateLifecycleDirtied; [_superview dirtyLayout]; } } - (BOOL)isLayoutDirty { - return _layoutLifecycle != RCTLayoutLifecycleComputed; + return _layoutLifecycle != RCTUpdateLifecycleComputed; } - (void)dirtyPropagation { - if (_propagationLifecycle != RCTPropagationLifecycleDirtied) { - _propagationLifecycle = RCTPropagationLifecycleDirtied; + if (_propagationLifecycle != RCTUpdateLifecycleDirtied) { + _propagationLifecycle = RCTUpdateLifecycleDirtied; [_superview dirtyPropagation]; } } - (BOOL)isPropagationDirty { - return _propagationLifecycle != RCTLayoutLifecycleComputed; + return _propagationLifecycle != RCTUpdateLifecycleComputed; } - (void)dirtyText { - if (_textLifecycle != RCTTextLifecycleDirtied) { - _textLifecycle = RCTTextLifecycleDirtied; + if (_textLifecycle != RCTUpdateLifecycleDirtied) { + _textLifecycle = RCTUpdateLifecycleDirtied; [_superview dirtyText]; } } - (BOOL)isTextDirty { - return _textLifecycle != RCTTextLifecycleComputed; + return _textLifecycle != RCTUpdateLifecycleComputed; } - (void)setTextComputed { - _textLifecycle = RCTTextLifecycleComputed; + _textLifecycle = RCTUpdateLifecycleComputed; } - (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex @@ -387,7 +336,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st return [shadowView reactTagAtPoint:relativePoint]; } } - return self.reactTag; } diff --git a/ReactKit/Views/RCTStaticImageManager.m b/ReactKit/Views/RCTStaticImageManager.m index 2cd08e1e2..84b782b1d 100644 --- a/ReactKit/Views/RCTStaticImageManager.m +++ b/ReactKit/Views/RCTStaticImageManager.m @@ -16,8 +16,7 @@ RCT_EXPORT_VIEW_PROPERTY(capInsets) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode) - -- (void)set_src:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(src, RCTStaticImage *) { if (json) { if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) { @@ -29,8 +28,7 @@ RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode) view.image = defaultView.image; } } - -- (void)set_tintColor:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(tintColor, RCTStaticImage *) { if (json) { view.renderingMode = UIImageRenderingModeAlwaysTemplate; diff --git a/ReactKit/Views/RCTTextField.h b/ReactKit/Views/RCTTextField.h index 2a0225f27..1688f8fdc 100644 --- a/ReactKit/Views/RCTTextField.h +++ b/ReactKit/Views/RCTTextField.h @@ -10,6 +10,6 @@ @property (nonatomic, assign) BOOL autoCorrect; @property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher; +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; @end diff --git a/ReactKit/Views/RCTTextField.m b/ReactKit/Views/RCTTextField.m index 4a70e0460..b684517b3 100644 --- a/ReactKit/Views/RCTTextField.m +++ b/ReactKit/Views/RCTTextField.m @@ -14,11 +14,6 @@ BOOL _jsRequestingFirstResponder; } -- (instancetype)initWithFrame:(CGRect)frame -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:CGRectZero])) { @@ -44,7 +39,7 @@ - (void)removeReactSubview:(UIView *)subview { - // TODO: this is a bit broken - if the TextView inserts any of + // TODO: this is a bit broken - if the TextField inserts any of // it's own views below or between React's, the indices won't match [_reactSubviews removeObject:subview]; [subview removeFromSuperview]; @@ -52,7 +47,7 @@ - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { - // TODO: this is a bit broken - if the TextView inserts any of + // TODO: this is a bit broken - if the TextField inserts any of // it's own views below or between React's, the indices won't match [_reactSubviews insertObject:view atIndex:atIndex]; [super insertSubview:view atIndex:atIndex]; diff --git a/ReactKit/Views/RCTTextFieldManager.m b/ReactKit/Views/RCTTextFieldManager.m index 368bf1d7d..5cdfd43a5 100644 --- a/ReactKit/Views/RCTTextFieldManager.m +++ b/ReactKit/Views/RCTTextFieldManager.m @@ -4,6 +4,7 @@ #import "RCTConvert.h" #import "RCTShadowView.h" +#import "RCTSparseArray.h" #import "RCTTextField.h" @implementation RCTTextFieldManager @@ -15,38 +16,34 @@ RCT_EXPORT_VIEW_PROPERTY(caretHidden) RCT_EXPORT_VIEW_PROPERTY(autoCorrect) +RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType) RCT_EXPORT_VIEW_PROPERTY(enabled) RCT_EXPORT_VIEW_PROPERTY(placeholder) RCT_EXPORT_VIEW_PROPERTY(text) RCT_EXPORT_VIEW_PROPERTY(font) RCT_EXPORT_VIEW_PROPERTY(clearButtonMode) -RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType) RCT_EXPORT_VIEW_PROPERTY(keyboardType) RCT_REMAP_VIEW_PROPERTY(color, textColor) - -- (void)set_fontSize:(id)json - forView:(RCTTextField *)view - withDefaultView:(RCTTextField *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(fontSize, RCTTextField *) { view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)]; } - -- (void)set_FontWeight:(id)json - forView:(RCTTextField *)view - withDefaultView:(RCTTextField *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(fontWeight, RCTTextField *) { view.font = [RCTConvert UIFont:view.font withWeight:json]; // TODO } - -- (void)set_fontFamily:(id)json - forView:(RCTTextField *)view - withDefaultView:(RCTTextField *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(fontFamily, RCTTextField *) { view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName]; } -// TODO: original code set view.paddingEdgeInsets from shadowView.paddingAsInsets -// could it be that this property is calculated asynchrously on shadow thread? +- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView +{ + NSNumber *reactTag = shadowView.reactTag; + UIEdgeInsets padding = shadowView.paddingAsInsets; + return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + ((RCTTextField *)viewRegistry[reactTag]).paddingEdgeInsets = padding; + }; +} @end - diff --git a/ReactKit/Views/RCTUIActivityIndicatorViewManager.m b/ReactKit/Views/RCTUIActivityIndicatorViewManager.m index b7e0971e7..e56f01106 100644 --- a/ReactKit/Views/RCTUIActivityIndicatorViewManager.m +++ b/ReactKit/Views/RCTUIActivityIndicatorViewManager.m @@ -13,12 +13,9 @@ RCT_EXPORT_VIEW_PROPERTY(activityIndicatorViewStyle) RCT_EXPORT_VIEW_PROPERTY(color) - -- (void)set_animating:(NSNumber *)value - forView:(UIActivityIndicatorView *)view - withDefaultView:(UIActivityIndicatorView *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(animating, UIActivityIndicatorView *) { - BOOL animating = value ? [value boolValue] : [defaultView isAnimating]; + BOOL animating = json ? [json boolValue] : [defaultView isAnimating]; if (animating != [view isAnimating]) { if (animating) { [view startAnimating]; @@ -28,7 +25,7 @@ RCT_EXPORT_VIEW_PROPERTY(color) } } -+ (NSDictionary *)constantsToExport +- (NSDictionary *)constantsToExport { return @{ diff --git a/ReactKit/Views/RCTView.h b/ReactKit/Views/RCTView.h index 77dee1df8..802336633 100644 --- a/ReactKit/Views/RCTView.h +++ b/ReactKit/Views/RCTView.h @@ -11,7 +11,6 @@ @interface RCTView : UIView @property (nonatomic, assign) RCTPointerEvents pointerEvents; -@property (nonatomic, copy) NSString *overrideAccessibilityLabel; + (void)autoAdjustInsetsForView:(UIView *)parentView withScrollView:(UIScrollView *)scrollView diff --git a/ReactKit/Views/RCTView.m b/ReactKit/Views/RCTView.m index abe6f00d9..dd0144009 100644 --- a/ReactKit/Views/RCTView.m +++ b/ReactKit/Views/RCTView.m @@ -34,8 +34,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view) - (NSString *)accessibilityLabel { - if (self.overrideAccessibilityLabel) { - return self.overrideAccessibilityLabel; + if (super.accessibilityLabel) { + return super.accessibilityLabel; } return RCTRecursiveAccessibilityLabel(self); } @@ -84,7 +84,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view) UIEdgeInsets baseInset = parentView.contentInset; CGFloat previousInsetTop = scrollView.contentInset.top; CGPoint contentOffset = scrollView.contentOffset; - + if (parentView.automaticallyAdjustContentInsets) { UIEdgeInsets autoInset = [self contentInsetsForView:parentView]; baseInset.top += autoInset.top; @@ -94,7 +94,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view) } [scrollView setContentInset:baseInset]; [scrollView setScrollIndicatorInsets:baseInset]; - + if (updateOffset) { // If we're adjusting the top inset, then let's also adjust the contentOffset so that the view // elements above the top guide do not cover the content. diff --git a/ReactKit/Views/RCTViewManager.h b/ReactKit/Views/RCTViewManager.h index e77be09c1..d3b7c8a01 100644 --- a/ReactKit/Views/RCTViewManager.h +++ b/ReactKit/Views/RCTViewManager.h @@ -2,9 +2,11 @@ #import +#import "RCTBridgeModule.h" #import "RCTConvert.h" #import "RCTLog.h" +@class RCTBridge; @class RCTEventDispatcher; @class RCTShadowView; @class RCTSparseArray; @@ -12,19 +14,22 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry); -@interface RCTViewManager : NSObject +@interface RCTViewManager : NSObject /** - * Designated initializer for view modules. Override this when subclassing. + * The bridge can be used to access both the RCTUIIManager and the RCTEventDispatcher, + * allowing the manager (or the views that it manages) to manipulate the view + * hierarchy and send events back to the JS context. */ -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; +@property (nonatomic, strong) RCTBridge *bridge; /** * The event dispatcher is used to send events back to the JavaScript application. * It can either be used directly by the module, or passed on to instantiated * view subclasses so that they can handle their own events. */ -@property (nonatomic, readonly, weak) RCTEventDispatcher *eventDispatcher; +// TODO: remove this, as it can be accessed directly from bridge +@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher; /** * The module name exposed to React JS. If omitted, this will be inferred @@ -88,34 +93,19 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v + (NSDictionary *)customDirectEventTypes; /** - * Injects constants into JS. These constants are made accessible via - * NativeModules.moduleName.X. Note that this method is not inherited when you - * subclass a view module, and you should not call [super constantsToExport] - * when overriding it. + * Called to notify manager that layout has finished, in case any calculated + * properties need to be copied over from shadow view to view. */ -+ (NSDictionary *)constantsToExport; +- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView; /** - * To deprecate, hopefully + * Called after view hierarchy manipulation has finished, and all shadow props + * have been set, but before layout has been performed. Useful for performing + * custo layout logic or tasks that involve walking the view hierarchy. + * To be deprecated, hopefully. */ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry; -/** - * Informal protocol for setting view and shadowView properties. - * Implement methods matching these patterns to set any properties that - * require special treatment (e.g. where the type or name cannot be inferred). - * - * - (void)set_:(id)property - * forView:(UIView *)view - * withDefaultView:(UIView *)defaultView; - * - * - (void)set_:(id)property - * forShadowView:(RCTShadowView *)view - * withDefaultView:(RCTShadowView *)defaultView; - * - * For simple cases, use the macros below: - */ - /** * This handles the simple case, where JS and native property names match * And the type can be automatically inferred. @@ -131,10 +121,21 @@ RCT_REMAP_VIEW_PROPERTY(name, name) - (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \ if ((json && !RCTSetProperty(view, @#keypath, json)) || \ (!json && !RCTCopyProperty(view, defaultView, @#keypath))) { \ - RCTLogMustFix(@"%@ does not have setter for `%s` property", [view class], #name); \ + RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \ } \ } +/** + * These macros can be used when you need to provide custom logic for setting + * view properties. The macro should be followed by a method body, which can + * refer to "json", "view" and "defaultView" to implement the required logic. + */ +#define RCT_CUSTOM_VIEW_PROPERTY(name, viewType) \ +- (void)set_##name:(id)json forView:(viewType)view withDefaultView:(viewType)defaultView + +#define RCT_CUSTOM_SHADOW_PROPERTY(name, viewType) \ +- (void)set_##name:(id)json forShadowView:(viewType)view withDefaultView:(viewType)defaultView + /** * These are useful in cases where the module's superclass handles a * property, but you wish to "unhandle" it, so it will be ignored. diff --git a/ReactKit/Views/RCTViewManager.m b/ReactKit/Views/RCTViewManager.m index 05ecc2198..d534e0e8b 100644 --- a/ReactKit/Views/RCTViewManager.m +++ b/ReactKit/Views/RCTViewManager.m @@ -2,32 +2,33 @@ #import "RCTViewManager.h" +#import "RCTBridge.h" #import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTShadowView.h" +#import "RCTUtils.h" #import "RCTView.h" @implementation RCTViewManager -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +@synthesize bridge = _bridge; + +- (RCTEventDispatcher *)eventDispatcher { - if ((self = [super init])) { - _eventDispatcher = eventDispatcher; - } - return self; + return _bridge.eventDispatcher; } + (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]; } - if ([name hasSuffix:@"Manager"]) { - name = [name substringToIndex:name.length - @"Manager".length]; - } return name; } @@ -51,7 +52,12 @@ return nil; } -+ (NSDictionary *)constantsToExport +- (NSDictionary *)constantsToExport +{ + return nil; +} + +- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView { return nil; } @@ -77,28 +83,22 @@ RCT_REMAP_VIEW_PROPERTY(borderColor, layer.borderColor); RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius) RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth) RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform) - -- (void)set_overflow:(id)json - forView:(UIView *)view - withDefaultView:(UIView *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(overflow, UIView *) { view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds; } - -- (void)set_pointerEvents:(id)json - forView:(RCTView *)view - withDefaultView:(RCTView *)defaultView +RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTView *) { if ([view respondsToSelector:@selector(setPointerEvents:)]) { view.pointerEvents = json ? [RCTConvert RCTPointerEvents:json] : defaultView.pointerEvents; return; } - + if (!json) { view.userInteractionEnabled = defaultView.userInteractionEnabled; return; } - + switch ([RCTConvert NSInteger:json]) { case RCTPointerEventsUnspecified: // Pointer events "unspecified" acts as if a stylesheet had not specified, @@ -117,54 +117,34 @@ RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform) // ShadowView properties -- (void)set_backgroundColor:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, RCTShadowView *) { - shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor; - shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet; + view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor; + view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet; } - -- (void)set_flexDirection:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(flexDirection, RCTShadowView *) { - shadowView.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection; + view.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection; } - -- (void)set_flexWrap:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(flexWrap, RCTShadowView *) { - shadowView.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap; + view.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap; } - -- (void)set_justifyContent:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(justifyContent, RCTShadowView *) { - shadowView.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent; + view.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent; } - -- (void)set_alignItems:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(alignItems, RCTShadowView *) { - shadowView.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems; + view.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems; } - -- (void)set_alignSelf:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(alignSelf, RCTShadowView *) { - shadowView.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf; + view.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf; } - -- (void)set_position:(id)json - forShadowView:(RCTShadowView *)shadowView - withDefaultView:(RCTShadowView *)defaultView +RCT_CUSTOM_SHADOW_PROPERTY(position, RCTShadowView *) { - shadowView.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType; + view.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType; } @end diff --git a/ReactKit/Views/RCTWrapperViewController.h b/ReactKit/Views/RCTWrapperViewController.h index d8f22270a..69075c6a4 100644 --- a/ReactKit/Views/RCTWrapperViewController.h +++ b/ReactKit/Views/RCTWrapperViewController.h @@ -15,8 +15,11 @@ didMoveToNavigationController:(UINavigationController *)navigationController; @interface RCTWrapperViewController : UIViewController -- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher; -- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTEventDispatcher *)eventDispatcher; +- (instancetype)initWithContentView:(UIView *)contentView + eventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithNavItem:(RCTNavItem *)navItem + eventDispatcher:(RCTEventDispatcher *)eventDispatcher; @property (nonatomic, readwrite, weak) id navigationListener; @property (nonatomic, strong, readwrite) RCTNavItem *navItem; diff --git a/ReactKit/Views/RCTWrapperViewController.m b/ReactKit/Views/RCTWrapperViewController.m index d027dc2f2..6b58d6631 100644 --- a/ReactKit/Views/RCTWrapperViewController.m +++ b/ReactKit/Views/RCTWrapperViewController.m @@ -15,11 +15,6 @@ CGFloat _previousBottomLayout; } -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - RCT_NOT_DESIGNATED_INITIALIZER(); -} - - (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithNibName:nil bundle:nil])) {