2015-02-04 updates

- Unbreak ReactKit | Nick Lockwood
- Refactor | Nick Lockwood
- [ReactNative] fix touch cancel behavior | Spencer Ahrens
- [ReactNative iOS] Fix responder issue with textInput | Eric Vicenti
- [ReactNative] README updates - file watching troubleshooting, one-time code drop -> currently private repo | Spencer Ahrens
- [ReactKit] Re-add README linebreaks | Ben Alpert
This commit is contained in:
Christopher Chedeau 2015-02-06 15:43:59 -08:00
parent 6153fffb30
commit fd8b7dee77
55 changed files with 1515 additions and 1668 deletions

View File

@ -16,7 +16,7 @@
* @providesModule ResponderEventPlugin
*/
"use strict";
'use strict';
var EventConstants = require('EventConstants');
var EventPluginUtils = require('EventPluginUtils');
@ -27,6 +27,7 @@ var ResponderSyntheticEvent = require('ResponderSyntheticEvent');
var ResponderTouchHistoryStore = require('ResponderTouchHistoryStore');
var accumulate = require('accumulate');
var invariant = require('invariant');
var keyOf = require('keyOf');
var isStartish = EventPluginUtils.isStartish;
@ -43,6 +44,12 @@ var executeDispatchesInOrderStopAtTrue =
*/
var responderID = null;
/**
* Count of current touches. A textInput should become responder iff the
* the selection changes while there is a touch on the screen.
*/
var trackedTouchCount = 0;
/**
* Last reported number of active touches.
*/
@ -426,7 +433,8 @@ function setResponderAndExtractTransfer(
function canTriggerTransfer(topLevelType, topLevelTargetID) {
return topLevelTargetID && (
topLevelType === EventConstants.topLevelTypes.topScroll ||
topLevelType === EventConstants.topLevelTypes.topSelectionChange ||
(trackedTouchCount > 0 &&
topLevelType === EventConstants.topLevelTypes.topSelectionChange) ||
isStartish(topLevelType) ||
isMoveish(topLevelType)
);
@ -489,6 +497,15 @@ var ResponderEventPlugin = {
topLevelTargetID,
nativeEvent) {
if (isStartish(topLevelType)) {
trackedTouchCount += 1;
} else if (isEndish(topLevelType)) {
trackedTouchCount -= 1;
invariant(
trackedTouchCount >= 0,
'Ended a touch event which was not counted in trackedTouchCount.'
);
}
ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);
@ -507,12 +524,10 @@ var ResponderEventPlugin = {
// (`onResponderRelease/onResponderTerminate`).
var isResponderTouchStart = responderID && isStartish(topLevelType);
var isResponderTouchMove = responderID && isMoveish(topLevelType);
var isResponderTouchTerminate = responderID && topLevelType === EventConstants.topLevelTypes.topTouchCancel;
var isResponderTouchEnd = responderID && isEndish(topLevelType);
var incrementalTouch =
isResponderTouchStart ? eventTypes.responderStart :
isResponderTouchMove ? eventTypes.responderMove :
isResponderTouchTerminate ? eventTypes.responderTerminate :
isResponderTouchEnd ? eventTypes.responderEnd :
null;
@ -524,17 +539,24 @@ var ResponderEventPlugin = {
extracted = accumulate(extracted, gesture);
}
var isResponderTerminate =
responderID &&
topLevelType === EventConstants.topLevelTypes.topTouchCancel;
var isResponderRelease =
responderID && isEndish(topLevelType) && noResponderTouches(nativeEvent);
if (isResponderRelease) {
var release = ResponderSyntheticEvent.getPooled(
eventTypes.responderRelease,
responderID,
nativeEvent
);
release.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(release);
extracted = accumulate(extracted, release);
responderID &&
!isResponderTerminate &&
isEndish(topLevelType) &&
noResponderTouches(nativeEvent);
var finalTouch =
isResponderTerminate ? eventTypes.responderTerminate :
isResponderRelease ? eventTypes.responderRelease :
null;
if (finalTouch) {
var finalEvent =
ResponderSyntheticEvent.getPooled(finalTouch, responderID, nativeEvent);
finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(finalEvent);
extracted = accumulate(extracted, finalEvent);
changeResponder(null);
}

View File

@ -0,0 +1,48 @@
/**
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ExecutionEnvironment
*
* Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>>
*/
/*jslint evil: true */
"use strict";
var canUseDOM = false;
/**
* Simple, lightweight module assisting with the detection and context of
* Worker. Helps avoid circular dependencies and allows code to reason about
* whether or not they are in a Worker, even if they never include the main
* `ReactWorker` dependency.
*/
var ExecutionEnvironment = {
canUseDOM: canUseDOM,
canUseWorkers: typeof Worker !== 'undefined',
canUseEventListeners:
canUseDOM && !!(window.addEventListener || window.attachEvent),
canUseViewport: canUseDOM && !!window.screen,
isInWorker: !canUseDOM // For now, this is true - might change in the future.
};
module.exports = ExecutionEnvironment;

View File

@ -2,13 +2,12 @@
* @providesModule Touchable
*/
"use strict";
'use strict';
var BoundingDimensions = require('BoundingDimensions');
var Position = require('Position');
var TouchEventUtils = require('TouchEventUtils');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
var queryLayoutByID = require('queryLayoutByID');
@ -277,20 +276,20 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10;
* +
* | RESPONDER_GRANT (HitRect)
* v
* +---------------------------+ DELAY +-------------------------+ T - DELAY +------------------------------+
* |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN|
* +---------------------------+ DELAY +-------------------------+ T - DELAY +------------------------------+
* |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN|
* +---------------------------+ +-------------------------+ +------------------------------+
* + ^ + ^ + ^
* |LEAVE_ |ENTER_ |LEAVE_ |ENTER_ |LEAVE_ |ENTER_
* |PRESS_RECT |PRESS_RECT |PRESS_RECT |PRESS_RECT |PRESS_RECT |PRESS_RECT
* | | | | | |
* v + v + v +
* +----------------------------+ DELAY +--------------------------+ +-------------------------------+
* |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT|
* +----------------------------+ +--------------------------+ +-------------------------------+
* +----------------------------+ DELAY +--------------------------+ +-------------------------------+
* |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT|
* +----------------------------+ +--------------------------+ +-------------------------------+
*
* T - DELAY => LONG_PRESS_THRESHOLD - DELAY
*
* T - DELAY => LONG_PRESS_THRESHOLD - DELAY
*
* Not drawn are the side effects of each transition. The most important side
* effect is the `touchableHandlePress` abstract method invocation that occurs
* when a responder is released while in either of the "Press" states.
@ -344,7 +343,7 @@ var TouchableMixin = {
*
*/
touchableHandleResponderGrant: function(e, dispatchID) {
// Since e is used in a callback invoked on another event loop
// Since e is used in a callback invoked on another event loop
// (as in setTimeout etc), we need to call e.persist() on the
// event to make sure it doesn't get reused in the event object pool.
e.persist();
@ -420,9 +419,9 @@ var TouchableMixin = {
var movedDistance = this._getDistanceBetweenPoints(pageX, pageY, this.pressInLocation.pageX, this.pressInLocation.pageY);
if (movedDistance > LONG_PRESS_ALLOWED_MOVEMENT) {
this._cancelLongPressDelayTimeout();
}
}
}
var isTouchWithinActive =
pageX > positionOnActivate.left - pressExpandLeft &&
pageY > positionOnActivate.top - pressExpandTop &&
@ -556,18 +555,19 @@ var TouchableMixin = {
*/
_receiveSignal: function(signal, e) {
var curState = this.state.touchable.touchState;
invariant(
Transitions[curState] && Transitions[curState][signal],
'You have supplied either an unrecognized signal or current state %s',
curState
);
if (!(Transitions[curState] && Transitions[curState][signal])) {
throw new Error(
'Unrecognized signal `' + signal + '` or state `' + curState +
'` for Touchable responder `' + this.state.touchable.responderID + '`'
);
}
var nextState = Transitions[curState][signal];
invariant(
nextState !== States.ERROR,
'Some assumptions about the state machine were violated. This is the ' +
'fault of Touchable.js. This case has been modeled and caught as an ' +
'error transition.'
);
if (nextState === States.ERROR) {
throw new Error(
'Touchable cannot transition from `' + curState + '` to `' + signal +
'` for responder `' + this.state.touchable.responderID + '`'
);
}
if (curState !== nextState) {
this._performSideEffectsForTransition(curState, nextState, signal, e);
this.state.touchable.touchState = nextState;
@ -580,7 +580,7 @@ var TouchableMixin = {
},
_isHighlight: function (state) {
return state === States.RESPONDER_ACTIVE_PRESS_IN ||
return state === States.RESPONDER_ACTIVE_PRESS_IN ||
state === States.RESPONDER_ACTIVE_LONG_PRESS_IN;
},
@ -626,15 +626,15 @@ var TouchableMixin = {
if (IsPressingIn[curState] && signal === Signals.LONG_PRESS_DETECTED) {
this.touchableHandleLongPress && this.touchableHandleLongPress();
}
}
if (newIsHighlight && !curIsHighlight) {
this._savePressInLocation(e);
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn();
} else if (!newIsHighlight && curIsHighlight) {
this.touchableHandleActivePressOut && this.touchableHandleActivePressOut();
}
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
var hasLongPressHandler = !!this.touchableHandleLongPress;
var pressIsLongButStillCallOnPress =
@ -660,4 +660,3 @@ var Touchable = {
};
module.exports = Touchable;

View File

@ -1,6 +1,6 @@
**Warning: This is a one-time code drop to accompany the ReactJS conference
**Warning: This is currently a private repo to accompany the ReactJS conference
talk, and is not accessible outside the official conference attendees - please
don't share this code.**
do not share this code.**
This is also a very early alpha release. There are certainly bugs and missing
features. Some things may even be well-documented in JS, but missing from the
@ -67,11 +67,14 @@ the same library.**
## Troubleshooting
Xcode will break if you have two examples open at the same time.
Jest testing does not yet work on node versions after 0.10.x.
You can verify the packager is working by loading the [bundle](http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle) in your browser and
+ Xcode will break if you have two examples open at the same time.
+ If `npm start` fails with log spew like:
```
2015-02-02 10:56 node[24294] (FSEvents.framework) FSEventStreamStart: register_with_server: ERROR: f2d_register_rpc() => (null) (-21)
```
then you've hit the node file watching bug - `brew install watchman` should fix the issue.
+ Jest testing does not yet work on node versions after 0.10.x.
+ You can verify the packager is working by loading the [bundle](http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle) in your browser and
inspecting the contents.
Please report any other issues you encounter so we can fix them ASAP.
@ -92,27 +95,27 @@ the responder system.
# FAQ
Q. How does debugging work? Can I set breakpoints in my JS?
Q. How does debugging work? Can I set breakpoints in my JS?
A. We are going to add the ability to use the Chrome developer tools soon. We
are very passionate about building the best possible developer experience.
Q. When is this coming to Android/Windows/OS X/etc?
Q. When is this coming to Android/Windows/OS X/etc?
A. We're working on Android, and we are excited to release it as soon as we can.
We are looking forward to the community helping us target other platforms as
well :)
Q. How do I create my own app?
Q. How do I create my own app?
A. Copy the entire `Examples/TicTacToe` folder, rename stuff in Xcode, and
replace the `TicTacToeApp.js` with your own. Then, in `AppDelegate.m`, update
`moduleName` to match your call to
`Bundler.registerComponent(<moduleName>, <componentName>)` at the bottom of your
JS file, and update `jsCodeLocation` to match your JS file name and location.
Q. Can I submit my own React Native app to the App Store?
Q. Can I submit my own React Native app to the App Store?
A. Not yet, but you will be able to soon. If you build something you want to
submit to the App Store, come talk to us ASAP.
Q. How do I deploy to my device?
Q. How do I deploy to my device?
A. You can change `localhost` in `AppDelegate.m` to your laptop's IP address and
grab the bundle over the same Wi-Fi network. You can also download the bundle
that the React packager generates, save it to the file `main.jsbundle`, and add it
@ -120,20 +123,20 @@ as a static resource in your Xcode project. Then set the `jsCodeLocation` in
`AppDelegate.m` to point to that file and deploy to your device like you would
any other app.
Q. What's up with this private repo? Why aren't you just open sourcing it now?
Q. What's up with this private repo? Why aren't you just open sourcing it now?
A. We want input from the React community before we open the floodgates so we
can incorporate your feedback, and we also have a bunch more features we want to
add to make a more complete offering before we open source.
Q. Do you have to ship a JS runtime with your apps?
Q. Do you have to ship a JS runtime with your apps?
A. No, we just use the JavaScriptCore public API that is part of iOS 7 and
later.
Q. How do I add more native capabilities?
Q. How do I add more native capabilities?
A. React Native is designed to be extensible - come talk to us, we would love to
work with you.
Q. Can I reuse existing iOS code?
Q. Can I reuse existing iOS code?
A. Yes, React Native is designed to be extensible and allow integration of all
sorts of native components, such as `UINavigationController` (available as
`<NavigatorIOS>`), `MKMapView` (not available yet), or your own custom

View File

@ -1,11 +1,9 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTExport.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"
@protocol RCTNativeModule;
@class RCTEventDispatcher;
@class RCTRootView;
@ -29,31 +27,65 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
}
/**
* Async batched bridge used to communicate with `RCTJavaScriptAppEngine`.
* Async batched bridge used to communicate with the JavaScript application.
*/
@interface RCTBridge : NSObject <RCTInvalidating>
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor;
/**
* 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.
*/
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor NS_DESIGNATED_INITIALIZER;
/**
* This method is used to call functions in the JavaScript application context.
* It is primarily intended for use by modules that require two-way communication
* with the JavaScript code.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
/**
* This method is used to execute a new application script. It is called
* internally whenever a JS application bundle is loaded/reloaded, but should
* probably not be used at any other time.
*/
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
/**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
* higher-level interface for sending UI events such as touches and text input.
*/
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
/**
* The shadow queue is used to execute callbacks from the JavaScript code. All
* native hooks (e.g. exported module methods) will be executed on the shadow
* queue.
*/
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
// 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 will print to both xcode and js debugger consoles.
* Global logging function that will print to both xcode and JS debugger consoles.
*
* NOTE: Use via RCTLog* macros defined in RCTLog.h
* TODO (#5906496): should log function be exposed here, or could it be a module?
*/
+ (void)log:(NSArray *)objects level:(NSString *)level;
/**
* Method to check that a valid executor exists with which to log
*/
+ (BOOL)hasValidJSExecutor;
@end

View File

@ -2,27 +2,18 @@
#import "RCTBridge.h"
#import <dlfcn.h>
#import <mach-o/getsect.h>
#import <mach-o/dyld.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import "RCTModuleMethod.h"
#import "RCTConvert.h"
#import "RCTInvalidating.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUtils.h"
/**
* Functions are the one thing that aren't automatically converted to OBJC
* blocks, according to this revert: http://trac.webkit.org/changeset/144489
* They must be expressed as `JSValue`s.
*
* But storing callbacks causes reference cycles!
* http://stackoverflow.com/questions/19202248/how-can-i-use-jsmanagedvalue-to-avoid-a-reference-cycle-without-the-jsvalue-gett
* We'll live with the leak for now, but need to clean this up asap:
* Passing a reference to the `context` to the bridge would make it easy to
* execute JS. We can add `JSManagedValue`s to protect against this. The same
* needs to be done in `RCTTiming` and friends.
*/
/**
* Must be kept in sync with `MessageQueue.js`.
*/
@ -35,13 +26,148 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};
static NSDictionary *RCTNativeModuleClasses(void)
/**
* 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 parses the exported methods inside RCTBridgeModules and
* generates a dictionary of arrays of RCTModuleMethod objects, keyed
* by module name.
*/
static NSDictionary *RCTExportedMethodsByModule(void)
{
static NSMutableDictionary *methodsByModule;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Dl_info info;
dladdr(&RCTExportedMethodsByModule, &info);
const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase;
const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport");
if (section == NULL) {
return;
}
methodsByModule = [NSMutableDictionary dictionary];
NSCharacterSet *plusMinusCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"+-"];
for (RCTExportValue addr = section->offset;
addr < section->offset + section->size;
addr += sizeof(id) * 2) {
const char **entry = (const char **)(mach_header + addr);
NSScanner *scanner = [NSScanner scannerWithString:@(entry[0])];
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 class = NSClassFromString(className);
if (class == Nil) continue;
SEL selector = NSSelectorFromString(selectorName);
Method method = ([plusMinus characterAtIndex:0] == '+' ? class_getClassMethod : class_getInstanceMethod)(class, 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];
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithSelector:selector
JSMethodName:JSMethodName
arity:method_getNumberOfArguments(method) - 2
blockArgumentIndexes:blockArgumentIndexes];
NSString *moduleName = [class respondsToSelector:@selector(moduleName)] ? [class moduleName] : className;
NSArray *moduleMap = methodsByModule[moduleName];
methodsByModule[moduleName] = (moduleMap != nil) ? [moduleMap arrayByAddingObject:moduleMethod] : @[moduleMethod];
}
});
return methodsByModule;
}
/**
* This function scans all classes available at runtime and returns a dictionary
* of classes that implement the RTCBridgeModule protocol, keyed by module name.
*/
static NSDictionary *RCTBridgeModuleClasses(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++) {
@ -53,17 +179,17 @@ static NSDictionary *RCTNativeModuleClasses(void)
continue;
}
if (![cls conformsToProtocol:@protocol(RCTNativeModule)]) {
// Not an RCTNativeModule
if (![cls conformsToProtocol:@protocol(RCTBridgeModule)]) {
// Not an RCTBridgeModule
continue;
}
// Get module name
NSString *moduleName = [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
// Check module name is unique
id existingClass = modules[moduleName];
RCTCAssert(existingClass == Nil, @"Attempted to register RCTNativeModule class %@ for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
RCTCAssert(existingClass == Nil, @"Attempted to register RCTBridgeModule class %@ for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
modules[moduleName] = cls;
}
@ -73,11 +199,166 @@ static NSDictionary *RCTNativeModuleClasses(void)
return modules;
}
/**
* This constructs the remote modules configuration data structure,
* which represents the native modules and methods that will be called
* by JS. A numeric ID is assigned to each module and method, which will
* be used to communicate via the bridge. The structure of each
* module is as follows:
*
* "ModuleName1": {
* "moduleID": 0,
* "methods": {
* "methodName1": {
* "methodID": 0,
* "type": "remote"
* },
* "methodName2": {
* "methodID": 1,
* "type": "remote"
* },
* etc...
* },
* "constants": {
* ...
* }
* },
* etc...
*/
static NSMutableDictionary *RCTRemoteModulesByID;
static NSDictionary *RCTRemoteModulesConfig()
{
static NSMutableDictionary *remoteModules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTRemoteModulesByID = [[NSMutableDictionary alloc] init];
remoteModules = [[NSMutableDictionary alloc] init];
[RCTExportedMethodsByModule() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, NSArray *rawMethods, BOOL *stop) {
NSMutableDictionary *methods = [NSMutableDictionary dictionaryWithCapacity:rawMethods.count];
[rawMethods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *stop) {
methods[method.JSMethodName] = @{
@"methodID": @(methodID),
@"type": @"remote",
};
}];
NSDictionary *module = @{
@"moduleID": @(remoteModules.count),
@"methods": methods
};
Class cls = RCTBridgeModuleClasses()[moduleName];
if (RCTClassOverridesClassMethod(cls, @selector(constantsToExport))) {
module = [module mutableCopy];
((NSMutableDictionary *)module)[@"constants"] = [cls constantsToExport];
}
remoteModules[moduleName] = module;
// Add module lookup
RCTRemoteModulesByID[module[@"moduleID"]] = moduleName;
}];
});
return remoteModules;
}
/**
* As above, but for local modules/methods, which represent JS classes
* and methods that will be called by the native code via the bridge.
* Structure is essentially the same as for remote modules:
*
* "ModuleName1": {
* "moduleID": 0,
* "methods": {
* "methodName1": {
* "methodID": 0,
* "type": "local"
* },
* "methodName2": {
* "methodID": 1,
* "type": "local"
* },
* etc...
* }
* },
* etc...
*/
static NSMutableDictionary *RCTLocalModuleIDs;
static NSMutableDictionary *RCTLocalMethodIDs;
static NSDictionary *RCTLocalModulesConfig()
{
static NSMutableDictionary *localModules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
NSMutableArray *JSMethods = [[NSMutableArray alloc] init];
// Add globally used methods
[JSMethods addObjectsFromArray:@[
@"Bundler.runApplication",
@"RCTEventEmitter.receiveEvent",
@"RCTEventEmitter.receiveTouches",
]];
// NOTE: these methods are currently unused in the OSS project
// @"Dimensions.set",
// @"RCTDeviceEventEmitter.emit",
// @"RCTNativeAppEventEmitter.emit",
// @"ReactIOS.unmountComponentAtNodeAndRemoveContainer",
// Register individual methods from modules
for (Class cls in RCTBridgeModuleClasses().allValues) {
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[JSMethods addObjectsFromArray:[cls JSMethods]];
}
}
localModules = [[NSMutableDictionary alloc] init];
for (NSString *moduleDotMethod in JSMethods) {
NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
RCTCAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);
// Add module if it doesn't already exist
NSString *moduleName = parts[0];
NSDictionary *module = localModules[moduleName];
if (!module) {
module = @{
@"moduleID": @(localModules.count),
@"methods": [[NSMutableDictionary alloc] init]
};
localModules[moduleName] = module;
}
// Add method if it doesn't already exist
NSString *methodName = parts[1];
NSMutableDictionary *methods = module[@"methods"];
if (!methods[methodName]) {
methods[methodName] = @{
@"methodID": @(methods.count),
@"type": @"local"
};
}
// Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
}
});
return localModules;
}
@implementation RCTBridge
{
NSMutableDictionary *_moduleInstances;
NSMutableDictionary *_moduleIDLookup;
NSMutableDictionary *_methodIDLookup;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
}
@ -90,10 +371,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Instantiate modules
_moduleInstances = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
[RCTBridgeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) {
if ([moduleClass instancesRespondToSelector:@selector(initWithBridge:)]) {
_moduleInstances[moduleName] = [[moduleClass alloc] initWithBridge:self];
@ -103,17 +384,27 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}];
_moduleIDLookup = [[NSMutableDictionary alloc] init];
_methodIDLookup = [[NSMutableDictionary alloc] init];
[self doneRegisteringModules];
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(),
@"localModulesConfig": RCTLocalModulesConfig()
}, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
dispatch_semaphore_signal(semaphore);
}];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogMustFix(@"JavaScriptExecutor took too long to inject JSON object");
}
}
return self;
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc; TODO: why not call it here then?");
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
#pragma mark - RCTInvalidating
@ -129,7 +420,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_latestJSExecutor = nil;
}
_javaScriptExecutor = nil;
dispatch_sync(_shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
// TODO: is this still needed?
@ -157,11 +448,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
NSNumber *moduleID = _moduleIDLookup[moduleDotMethod];
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID, @"Module '%@' not registered.",
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
NSNumber *methodID = _methodIDLookup[moduleDotMethod];
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID, @"Method '%@' not registered.", moduleDotMethod);
[self _invokeAndProcessModule:@"BatchedBridge"
@ -177,7 +468,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
onComplete(scriptLoadError);
return;
}
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
method:@"flushedQueue"
arguments:@[]
@ -193,19 +484,19 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
NSTimeInterval startJS = RCTTGetAbsoluteTime();
RCTJavaScriptCallback processResponse = ^(id objcValue, NSError *error) {
NSTimeInterval startNative = RCTTGetAbsoluteTime();
[self _handleBuffer:objcValue];
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)}];
};
[_javaScriptExecutor executeJSCall:module
method:method
arguments:args
@ -229,12 +520,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
if (buffer == nil || buffer == (id)kCFNull) {
return;
}
if (![buffer isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
return;
}
NSArray *requestsArray = (NSArray *)buffer;
NSUInteger bufferRowCount = [requestsArray count];
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
@ -242,7 +533,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
RCTLogMustFix(@"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]]) {
@ -250,27 +541,27 @@ static id<RCTJavaScriptExecutor> _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) {
RCTLogMustFix(@"Invalid data message - all must be length: %zd", numRequests);
return;
}
for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool {
[self _handleRequestNumber:i
moduleID:[moduleIDs[i] integerValue]
moduleID:moduleIDs[i]
methodID:[methodIDs[i] integerValue]
params:paramsArrays[i]];
}
}
// TODO: only used by RCTUIManager - can we eliminate this special case?
dispatch_async(_shadowQueue, ^{
for (id target in _moduleInstances.objectEnumerator) {
@ -282,7 +573,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSInteger)moduleID
moduleID:(NSNumber *)moduleID
methodID:(NSInteger)methodID
params:(NSArray *)params
{
@ -291,13 +582,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return NO;
}
if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
NSString *moduleName = RCTRemoteModulesByID[moduleID];
if (!moduleName) {
RCTLogMustFix(@"Unknown moduleID: %@", moduleID);
return NO;
}
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(moduleID);
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
if (methodID < 0 || methodID >= methods.count) {
if (methodID >= methods.count) {
RCTLogMustFix(@"Unknown methodID: %zd for module: %@", methodID, moduleName);
return NO;
}
@ -321,13 +614,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// 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->_moduleInstances[moduleName];
RCTAssert(target != nil, @"No module found for name '%@'", moduleName);
SEL selector = method.selector;
NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setArgument:&target atIndex:0];
@ -335,7 +627,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// 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;
@ -344,9 +636,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[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]) {
@ -357,7 +651,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
shouldSet = NO;
}
break;
case '*':
if ([param isKindOfClass:[NSString class]]) {
const char *string = [param UTF8String];
@ -365,36 +659,38 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
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)
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:&param atIndex:argIdx];
[invocation setArgument:&param atIndex:argIdx];
}
}];
@ -420,7 +716,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
if (!cbID) {
return nil;
}
return ^(NSArray *args) {
[self _sendResponseToJavaScriptCallbackID:cbID args:args];
};
@ -433,7 +729,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
dispatch_once(&onceToken, ^{
invocations = [NSMutableDictionary dictionary];
});
id key = @(argCount);
NSInvocation *invocation = invocations[key];
if (invocation == nil) {
@ -442,170 +738,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocations[key] = invocation;
}
return invocation;
}
- (NSArray *)JSMethods
{
NSMutableArray *methods = [[NSMutableArray alloc] init];
// Add globally used methods
[methods addObjectsFromArray:@[
@"Bundler.runApplication",
@"RCTEventEmitter.receiveEvent",
@"RCTEventEmitter.receiveTouches",
]];
// NOTE: these methods are currently unused in the OSS project
// @"Dimensions.set",
// @"RCTDeviceEventEmitter.emit",
// @"RCTNativeAppEventEmitter.emit",
// @"ReactIOS.unmountComponentAtNodeAndRemoveContainer",
// Register individual methods from modules
for (Class cls in RCTNativeModuleClasses().allValues) {
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[methods addObjectsFromArray:[cls JSMethods]];
}
}
return methods;
}
- (void)doneRegisteringModules
{
// TODO: everything in this method can actually be determined
// statically from just the class, and can therefore be cached
// in a dispatch_once instead of being repeated every time we
// reload or create a new RootView.
RCTAssertMainThread();
/**
* This constructs the remote modules configuration data structure,
* which represents the native modules and methods that will be called
* by JS. A numeric ID is assigned to each module and method, which will
* be used to communicate via the bridge. The structure of each
* module is as follows:
*
* "ModuleName1": {
* "moduleID": 0,
* "methods": {
* "methodName1": {
* "methodID": 0,
* "type": "remote"
* },
* "methodName2": {
* "methodID": 1,
* "type": "remote"
* },
* etc...
* },
* "constants": {
* ...
* }
* },
* etc...
*/
NSUInteger moduleCount = RCTExportedMethodsByModule().count;
NSMutableDictionary *remoteModules = [NSMutableDictionary dictionaryWithCapacity:RCTExportedMethodsByModule().count];
for (NSUInteger i = 0; i < moduleCount; i++) {
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(i);
NSArray *rawMethods = RCTExportedMethodsByModule()[moduleName];
NSMutableDictionary *methods = [NSMutableDictionary dictionaryWithCapacity:rawMethods.count];
[rawMethods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *stop) {
methods[method.JSMethodName] = @{
@"methodID": @(methodID),
@"type": @"remote",
};
}];
NSDictionary *module = @{
@"moduleID": @(i),
@"methods": methods
};
id target = _moduleInstances[moduleName];
if (RCTClassOverridesClassMethod([target class], @selector(constantsToExport))) {
module = [module mutableCopy];
((NSMutableDictionary *)module)[@"constants"] = [[target class] constantsToExport];
}
remoteModules[moduleName] = module;
}
/**
* As above, but for local modules/methods, which represent JS classes
* and methods that will be called by the native code via the bridge.
* Structure is essentially the same as for remote modules:
*
* "ModuleName1": {
* "moduleID": 0,
* "methods": {
* "methodName1": {
* "methodID": 0,
* "type": "local"
* },
* "methodName2": {
* "methodID": 1,
* "type": "local"
* },
* etc...
* }
* },
* etc...
*/
NSMutableDictionary *localModules = [[NSMutableDictionary alloc] init];
for (NSString *moduleDotMethod in [self JSMethods]) {
NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
RCTAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);
// Add module if it doesn't already exist
NSString *moduleName = parts[0];
NSDictionary *module = localModules[moduleName];
if (!module) {
module = @{
@"moduleID": @(localModules.count),
@"methods": [[NSMutableDictionary alloc] init]
};
localModules[moduleName] = module;
}
// Add method if it doesn't already exist
NSString *methodName = parts[1];
NSMutableDictionary *methods = module[@"methods"];
if (!methods[methodName]) {
methods[methodName] = @{
@"methodID": @(methods.count),
@"type": @"local"
};
}
// Add module and method lookup
_moduleIDLookup[moduleDotMethod] = module[@"moduleID"];
_methodIDLookup[moduleDotMethod] = methods[methodName][@"methodID"];
}
/**
* Inject module data into JS context
*/
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": remoteModules,
@"localModulesConfig": localModules
}, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
dispatch_semaphore_signal(semaphore);
}];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogMustFix(@"JavaScriptExecutor take too long to inject JSON object");
}
}
- (void)registerRootView:(RCTRootView *)rootView
{
// TODO: only used by RCTUIManager - can we eliminate this special case?
@ -628,7 +764,7 @@ static id<RCTJavaScriptExecutor> _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]]) {

View File

@ -0,0 +1,65 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
@class RCTBridge;
/**
* The type of a block that is capable of sending a response to a bridged
* operation. Use this for returning callback methods to JS.
*/
typedef void (^RCTResponseSenderBlock)(NSArray *response);
/**
* Provides minimal interface needed to register a bridge module
*/
@protocol RCTBridgeModule <NSObject>
@optional
/**
* Optional initializer for modules that require access
* to bridge features, such as sending events or making JS calls
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
* The module name exposed to JS. If omitted, this will be inferred
* automatically by using the native module's class name.
*/
+ (NSString *)moduleName;
/**
* Place this macro inside the method body of any method you want to expose
* to JS. The optional js_name argument will be used as the JS method name
* (the method will be namespaced to the module name, as specified above).
* If omitted, the JS method name will match the first part of the Objective-C
* method selector name (up to the first colon).
*/
#define RCT_EXPORT(js_name) __attribute__((used, section("__DATA,RCTExport" \
))) static const char *__rct_export_entry__[] = { __func__, #js_name }
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.moduleName.X. Note that this method is not inherited when you
* subclass a module, and you should not call [super constantsToExport] when
* implementing it.
*/
+ (NSDictionary *)constantsToExport;
/**
* An array of JavaScript methods that the module will call via the
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a
* method that has not been registered will result in an error. If a method
* has already been regsistered by another module, it is not necessary to
* register it again, but it is good pratice. Registering the same method
* more than once is silently ignored and will not result in an error.
*/
+ (NSArray *)JSMethods;
/**
* Notifies the module that a batch of JS method invocations has just completed.
*/
- (void)batchDidComplete;
@end

View File

@ -4,7 +4,13 @@
#import <UIKit/UIKit.h>
#import "Layout.h"
#import "RCTPointerEvents.h"
/**
* This class provides a collection of conversion functions for mapping
* JSON objects to native types and classes. These are useful when writing
* custom RCTViewManager setter methods.
*/
@interface RCTConvert : NSObject
+ (BOOL)BOOL:(id)json;
@ -57,4 +63,21 @@
+ (css_position_type_t)css_position_type_t:(id)json;
+ (css_wrap_type_t)css_wrap_type_t:(id)json;
+ (RCTPointerEvents)RCTPointerEvents:(id)json;
@end
/**
* This function will attempt to set a property using a json value by first
* inferring the correct type from all available information, and then
* applying an appropriate conversion method. If the property does not
* exist, or the type cannot be inferred, the function will return NO.
*/
BOOL RCTSetProperty(id target, NSString *keypath, id json);
/**
* This function attempts to copy a property from the source object to the
* destination object using KVC. If the property does not exist, or cannot
* be set, it will do nothing and return NO.
*/
BOOL RCTCopyProperty(id target, id source, NSString *keypath);

View File

@ -4,6 +4,7 @@
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <objc/message.h>
#import "RCTLog.h"
@ -41,7 +42,7 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
return default; \
} \
if ([json isKindOfClass:[NSNumber class]]) { \
if ([[mapping allValues] containsObject:json]) { \
if ([[mapping allValues] containsObject:json] || [json getter] == default) { \
return [json getter]; \
} \
RCTLogMustFix(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allValues]); \
@ -651,4 +652,253 @@ RCT_ENUM_CONVERTER(css_wrap_type_t, (@{
@"nowrap": @(CSS_NOWRAP)
}), CSS_NOWRAP, intValue)
RCT_ENUM_CONVERTER(RCTPointerEvents, (@{
@"none": @(RCTPointerEventsNone),
@"boxonly": @(RCTPointerEventsBoxOnly),
@"boxnone": @(RCTPointerEventsBoxNone)
}), RCTPointerEventsUnspecified, integerValue)
@end
static NSString *RCTGuessTypeEncoding(id target, NSString *key, id value, NSString *encoding)
{
// TODO (#5906496): handle more cases
if ([key rangeOfString:@"color" options:NSCaseInsensitiveSearch].location != NSNotFound) {
if ([target isKindOfClass:[CALayer class]]) {
return @(@encode(CGColorRef));
} else {
return @"@\"UIColor\"";
}
}
return nil;
}
static NSDictionary *RCTConvertValue(id value, NSString *encoding)
{
static NSDictionary *converters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
id (^numberConvert)(id) = ^(id val){
return [RCTConvert NSNumber:val];
};
id (^boolConvert)(id) = ^(id val){
return @([RCTConvert BOOL:val]);
};
// TODO (#5906496): add the rest of RCTConvert here
converters =
@{
@(@encode(char)): boolConvert,
@(@encode(int)): numberConvert,
@(@encode(short)): numberConvert,
@(@encode(long)): numberConvert,
@(@encode(long long)): numberConvert,
@(@encode(unsigned char)): numberConvert,
@(@encode(unsigned int)): numberConvert,
@(@encode(unsigned short)): numberConvert,
@(@encode(unsigned long)): numberConvert,
@(@encode(unsigned long long)): numberConvert,
@(@encode(float)): numberConvert,
@(@encode(double)): numberConvert,
@(@encode(bool)): boolConvert,
@(@encode(UIEdgeInsets)): ^(id val) {
return [NSValue valueWithUIEdgeInsets:[RCTConvert UIEdgeInsets:val]];
},
@(@encode(CGPoint)): ^(id val) {
return [NSValue valueWithCGPoint:[RCTConvert CGPoint:val]];
},
@(@encode(CGSize)): ^(id val) {
return [NSValue valueWithCGSize:[RCTConvert CGSize:val]];
},
@(@encode(CGRect)): ^(id val) {
return [NSValue valueWithCGRect:[RCTConvert CGRect:val]];
},
@(@encode(CGColorRef)): ^(id val) {
return (id)[RCTConvert CGColor:val];
},
@(@encode(CGAffineTransform)): ^(id val) {
return [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:val]];
},
@(@encode(CATransform3D)): ^(id val) {
return [NSValue valueWithCATransform3D:[RCTConvert CATransform3D:val]];
},
@"@\"NSString\"": ^(id val) {
return [RCTConvert NSString:val];
},
@"@\"NSURL\"": ^(id val) {
return [RCTConvert NSURL:val];
},
@"@\"UIColor\"": ^(id val) {
return [RCTConvert UIColor:val];
},
@"@\"UIImage\"": ^(id val) {
return [RCTConvert UIImage:val];
},
@"@\"NSDate\"": ^(id val) {
return [RCTConvert NSDate:val];
},
@"@\"NSTimeZone\"": ^(id val) {
return [RCTConvert NSTimeZone:val];
},
};
});
// Handle null values
if (value == [NSNull null] && ![encoding isEqualToString:@"@\"NSNull\""]) {
return nil;
}
// Convert value
id (^converter)(id) = converters[encoding];
return converter ? converter(value) : value;
}
BOOL RCTSetProperty(id target, NSString *keypath, id value)
{
// Split keypath
NSArray *parts = [keypath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
target = [target valueForKey:parts[i]];
if (!target) {
return NO;
}
}
// Check target class for property definition
NSString *encoding = nil;
objc_property_t property = class_getProperty([target class], [key UTF8String]);
if (property) {
// Get type info
char *typeEncoding = property_copyAttributeValue(property, "T");
encoding = @(typeEncoding);
free(typeEncoding);
} else {
// Check if setter exists
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
if (![target respondsToSelector:setter]) {
return NO;
}
// Get type of first method argument
Method method = class_getInstanceMethod([target class], setter);
char *typeEncoding = method_copyArgumentType(method, 2);
if (typeEncoding) {
encoding = @(typeEncoding);
free(typeEncoding);
}
}
if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) {
// Not enough info about the type encoding to be useful, so
// try to guess the type from the value and property name
encoding = RCTGuessTypeEncoding(target, key, value, encoding);
}
// Special case for numeric encodings, which may be enums
if ([value isKindOfClass:[NSString class]] &&
[@"iIsSlLqQ" containsString:[encoding substringToIndex:1]]) {
/**
* NOTE: the property names below may seem weird, but it's
* because they are tested as case-sensitive suffixes, so
* "apitalizationType" will match any of the following
*
* - capitalizationType
* - autocapitalizationType
* - autoCapitalizationType
* - titleCapitalizationType
* - etc.
*/
static NSDictionary *converters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
converters =
@{
@"apitalizationType": ^(id val) {
return [RCTConvert UITextAutocapitalizationType:val];
},
@"eyboardType": ^(id val) {
return [RCTConvert UIKeyboardType:val];
},
@"extAlignment": ^(id val) {
return [RCTConvert NSTextAlignment:val];
},
};
});
for (NSString *subkey in converters) {
if ([key hasSuffix:subkey]) {
NSInteger (^converter)(NSString *) = converters[subkey];
value = @(converter(value));
break;
}
}
}
// Another nasty special case
if ([target isKindOfClass:[UITextField class]]) {
static NSDictionary *specialCases = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
specialCases = @{
@"autocapitalizationType": ^(UITextField *f, NSInteger v){ f.autocapitalizationType = v; },
@"autocorrectionType": ^(UITextField *f, NSInteger v){ f.autocorrectionType = v; },
@"spellCheckingType": ^(UITextField *f, NSInteger v){ f.spellCheckingType = v; },
@"keyboardType": ^(UITextField *f, NSInteger v){ f.keyboardType = v; },
@"keyboardAppearance": ^(UITextField *f, NSInteger v){ f.keyboardAppearance = v; },
@"returnKeyType": ^(UITextField *f, NSInteger v){ f.returnKeyType = v; },
@"enablesReturnKeyAutomatically": ^(UITextField *f, NSInteger v){ f.enablesReturnKeyAutomatically = !!v; },
@"secureTextEntry": ^(UITextField *f, NSInteger v){ f.secureTextEntry = !!v; }};
});
void (^block)(UITextField *f, NSInteger v) = specialCases[key];
if (block)
{
block(target, [value integerValue]);
return YES;
}
}
// Set converted value
[target setValue:RCTConvertValue(value, encoding) forKey:key];
return YES;
}
BOOL RCTCopyProperty(id target, id source, NSString *keypath)
{
// Split keypath
NSArray *parts = [keypath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
source = [source valueForKey:parts[i]];
target = [target valueForKey:parts[i]];
if (!source || !target) {
return NO;
}
}
// Check class for property definition
if (!class_getProperty([source class], [key UTF8String])) {
// Check if setter exists
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
if (![source respondsToSelector:setter]
|| ![target respondsToSelector:setter]) {
return NO;
}
}
[target setValue:[source valueForKey:key] forKey:key];
return YES;
}

View File

@ -4,13 +4,6 @@
@class RCTBridge;
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
RCTTouchEventTypeStart,
RCTTouchEventTypeMove,
RCTTouchEventTypeEnd,
RCTTouchEventTypeCancel
};
typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeFocus,
RCTTextEventTypeBlur,
@ -39,13 +32,6 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
*/
- (void)sendEventWithName:(NSString *)name body:(NSDictionary *)body;
/**
* Send an array of touch events
*/
- (void)sendTouchEventWithType:(RCTTouchEventType)type
touches:(NSArray *)touches
changedIndexes:(NSArray *)changedIndexes;
/**
* Send text events
*/

View File

@ -2,6 +2,7 @@
#import "RCTEventDispatcher.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "UIView+ReactKit.h"
@ -27,34 +28,6 @@
args:@[body[@"target"], name, body]];
}
/**
* Constructs information about touch events to send across the serialized
* boundary. This data should be compliant with W3C `Touch` objects. This data
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
* there must be a simple receiver on the other side of the bridge that
* organizes the touch objects into `Event`s.
*
* We send the data as an array of `Touch`es, the type of action
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
* from that array.
*/
- (void)sendTouchEventWithType:(RCTTouchEventType)type
touches:(NSArray *)touches
changedIndexes:(NSArray *)changedIndexes
{
static NSString *events[] = {
@"topTouchStart",
@"topTouchMove",
@"topTouchEnd",
@"topTouchCancel",
};
RCTAssert(touches.count, @"No touches in touchEventArgsForOrderedTouches");
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
args:@[events[type], touches, changedIndexes]];
}
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text

View File

@ -1,226 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import "RCTLog.h"
@class RCTBridge;
@class RCTSparseArray;
@class RCTUIManager;
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
@class RCTEventDispatcher;
@class RCTShadowView;
/* ------------------------------------------------------------------- */
typedef struct {
const char *func;
const char *js_name;
} RCTExportEntry;
#define _RCTExportSegmentName "__DATA"
#define _RCTExportSectionName "RCTExport"
extern NSString *RCTExportedModuleNameAtSortedIndex(NSUInteger index);
extern NSDictionary *RCTExportedMethodsByModule(void);
extern BOOL RCTSetProperty(id target, NSString *keypath, id value);
extern BOOL RCTCopyProperty(id target, id source, NSString *keypath);
extern BOOL RCTCallSetter(id target, SEL setter, id value);
/* ------------------------------------------------------------------- */
/**
* The type of a block that is capable of sending a response to a bridged
* operation. Use this for returning callback methods to JS.
*/
typedef void (^RCTResponseSenderBlock)(NSArray *response);
/**
* Provides minimal interface needed to register a bridge module
*/
@protocol RCTNativeModule <NSObject>
@optional
/**
* Optional initializer for modules that require access
* to bridge features, such as sending events or making JS calls
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
* Place this macro inside the method body of any method you want
* to expose to JS. The optional js_name argument will be used as
* the JS function name. If omitted, the JS function name will match
* the Objective-C method selector name, up to the first colon.
*/
#define RCT_EXPORT(js_name) __attribute__((used, section(_RCTExportSegmentName "," \
_RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __func__, #js_name }
/**
* The module name exposed to JS. If omitted, this will be inferred
* automatically by using the native module's class name.
*/
+ (NSString *)moduleName;
/**
* 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;
/**
* An array of JavaScript methods that the module will call via the
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a
* method that has not been registered will result in an error. If a method
* has already been regsistered by another module, it is not necessary to
* register it again, but it is good pratice. Registering the same method
* more than once is silently ignored and will not result in an error.
*/
+ (NSArray *)JSMethods;
/**
* Notifies the module that a batch of JS method invocations has just completed.
*/
- (void)batchDidComplete;
@end
/**
* Provides minimal interface needed to register a UIViewManager module
*/
@protocol RCTNativeViewModule <NSObject>
/**
* Designated initializer for view modules. The event dispatched can either be
* used directly by the module, or passed on to instantiated views for event handling.
*/
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
/**
* This method instantiates a native view to be managed by the module. The method
* will be called many times, and should return a fresh instance each time. The
* view module MUST NOT cache the returned view and return the same instance
* for subsequent calls.
*/
- (UIView *)view;
@optional
/**
* The module name exposed to JS. If omitted, this will be inferred
* automatically by using the view module's class name.
*/
+ (NSString *)moduleName;
/**
* This method instantiates a shadow view to be managed by the module. If omitted,
* an ordinary RCTShadowView instance will be created. As with the -view method,
* the -shadowView method should return a fresh instance each time it is called.
*/
- (RCTShadowView *)shadowView;
/**
* 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_<propertyName>:(id)property
* forView:(UIView *)view
* withDefaultView:(UIView *)defaultView;
*
* - (void)set_<propertyName>:(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.
*/
#define RCT_EXPORT_VIEW_PROPERTY(name) \
RCT_REMAP_VIEW_PROPERTY(name, name)
/**
* This macro maps a named property on the module to an arbitrary key path
* within the view.
*/
#define RCT_REMAP_VIEW_PROPERTY(name, keypath) \
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
if (json) { \
if(!RCTSetProperty(view, @#keypath, json)) { \
RCTLogMustFix(@"%@ does not have setter for `%s` property", [view class], #name); \
} \
} else { \
[view setValue:[defaultView valueForKeyPath:@#keypath] forKeyPath:@#keypath]; \
} \
}
/**
* These are useful in cases where the module's superclass handles a
* property, but you wish to "unhandle" it, so it will be ignored.
*/
#define RCT_IGNORE_VIEW_PROPERTY(name) \
- (void)set_##name:(id)value forView:(id)view withDefaultView:(id)defaultView {}
#define RCT_IGNORE_SHADOW_PROPERTY(name) \
- (void)set_##name:(id)value forShadowView:(id)view withDefaultView:(id)defaultView {}
/**
* Returns a dictionary of config data passed to JS that defines eligible events
* that can be placed on native views. This should return bubbling
* directly-dispatched event types and specify what names should be used to
* subscribe to either form (bubbling/capturing).
*
* Returned dictionary should be of the form: @{
* @"onTwirl": {
* @"phasedRegistrationNames": @{
* @"bubbled": @"onTwirl",
* @"captured": @"onTwirlCaptured"
* }
* }
* }
*
* Note that this method is not inherited when you subclass a view module, and
* you should not call [super customBubblingEventTypes] when implementing it.
*/
+ (NSDictionary *)customBubblingEventTypes;
/**
* Returns a dictionary of config data passed to JS that defines eligible events
* that can be placed on native views. This should return non-bubbling
* directly-dispatched event types.
*
* Returned dictionary should be of the form: @{
* @"onTwirl": {
* @"registrationName": @"onTwirl"
* }
* }
*
* Note that this method is not inherited when you subclass a view module, and
* you should not call [super customDirectEventTypes] when implementing it.
*/
+ (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 implementing it.
*/
+ (NSDictionary *)constantsToExport;
/**
* To deprecate, hopefully
*/
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry;
@end

View File

@ -1,392 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTExport.h"
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
#import <mach-o/getsect.h>
#import <mach-o/dyld.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import "RCTConvert.h"
#import "RCTModuleMethod.h"
#import "RCTUtils.h"
static NSDictionary *_methodsByModule;
@interface _RCTExportLoader : NSObject
@end
@implementation _RCTExportLoader
+ (NSString *)methodNameForSelector:(SEL)selector
{
NSString *methodName = NSStringFromSelector(selector);
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.location != NSNotFound) {
methodName = [methodName substringToIndex:colonRange.location];
}
return methodName;
}
+ (NSIndexSet *)blockArgumentIndexesForMethod:(Method)method
{
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);
}
return [blockArgumentIndexes copy];
}
+ (void)load
{
static uint32_t _exportsLoaded = 0;
if (OSAtomicTestAndSetBarrier(1, &_exportsLoaded)) {
return;
}
#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
Dl_info info;
dladdr(&RCTExportedMethodsByModule, &info);
const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase;
const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, _RCTExportSegmentName, _RCTExportSectionName);
if (section == NULL) {
return;
}
NSMutableDictionary *methodsByModule = [NSMutableDictionary dictionary];
NSCharacterSet *plusMinusCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"+-"];
for (RCTExportValue addr = section->offset;
addr < section->offset + section->size;
addr += sizeof(RCTExportEntry)) {
RCTExportEntry *entry = (RCTExportEntry *)(mach_header + addr);
NSScanner *scanner = [NSScanner scannerWithString:@(entry->func)];
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 class = NSClassFromString(className);
if (class == Nil) continue;
SEL selector = NSSelectorFromString(selectorName);
Method method = ([plusMinus characterAtIndex:0] == '+' ? class_getClassMethod : class_getInstanceMethod)(class, selector);
if (method == nil) continue;
NSString *JSMethodName = strlen(entry->js_name) ? @(entry->js_name) : [self methodNameForSelector:selector];
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithSelector:selector
JSMethodName:JSMethodName
arity:method_getNumberOfArguments(method) - 2
blockArgumentIndexes:[self blockArgumentIndexesForMethod:method]];
// TODO: store these by class name, not module name, then we don't need to call moduleName here
NSString *moduleName = [class respondsToSelector:@selector(moduleName)] ? [class moduleName] : className;
NSArray *moduleMap = methodsByModule[moduleName];
methodsByModule[moduleName] = (moduleMap != nil) ? [moduleMap arrayByAddingObject:moduleMethod] : @[moduleMethod];
}
_methodsByModule = [methodsByModule copy];
}
@end
NSDictionary *RCTExportedMethodsByModule(void)
{
return _methodsByModule;
}
NSString *RCTExportedModuleNameAtSortedIndex(NSUInteger index)
{
static dispatch_once_t onceToken;
static NSArray *sortedModuleNames;
dispatch_once(&onceToken, ^{
sortedModuleNames = [RCTExportedMethodsByModule().allKeys sortedArrayUsingSelector:@selector(compare:)];
});
return sortedModuleNames[index];
}
static NSString *RCTGuessTypeEncoding(id target, NSString *key, id value, NSString *encoding)
{
// TODO (#5906496): handle more cases
if ([key rangeOfString:@"color" options:NSCaseInsensitiveSearch].location != NSNotFound) {
if ([target isKindOfClass:[CALayer class]]) {
return @(@encode(CGColorRef));
} else {
return @"@\"UIColor\"";
}
}
return nil;
}
static NSDictionary *RCTConvertValue(id value, NSString *encoding)
{
static NSDictionary *converters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
id (^numberConvert)(id) = ^(id val){
return [RCTConvert NSNumber:val];
};
id (^boolConvert)(id) = ^(id val){
return @([RCTConvert BOOL:val]);
};
// TODO (#5906496): add the rest of RCTConvert here
converters =
@{
@(@encode(char)): boolConvert,
@(@encode(int)): numberConvert,
@(@encode(short)): numberConvert,
@(@encode(long)): numberConvert,
@(@encode(long long)): numberConvert,
@(@encode(unsigned char)): numberConvert,
@(@encode(unsigned int)): numberConvert,
@(@encode(unsigned short)): numberConvert,
@(@encode(unsigned long)): numberConvert,
@(@encode(unsigned long long)): numberConvert,
@(@encode(float)): numberConvert,
@(@encode(double)): numberConvert,
@(@encode(bool)): boolConvert,
@(@encode(UIEdgeInsets)): ^(id val) {
return [NSValue valueWithUIEdgeInsets:[RCTConvert UIEdgeInsets:val]];
},
@(@encode(CGPoint)): ^(id val) {
return [NSValue valueWithCGPoint:[RCTConvert CGPoint:val]];
},
@(@encode(CGSize)): ^(id val) {
return [NSValue valueWithCGSize:[RCTConvert CGSize:val]];
},
@(@encode(CGRect)): ^(id val) {
return [NSValue valueWithCGRect:[RCTConvert CGRect:val]];
},
@(@encode(CGColorRef)): ^(id val) {
return (id)[RCTConvert CGColor:val];
},
@(@encode(CGAffineTransform)): ^(id val) {
return [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:val]];
},
@(@encode(CATransform3D)): ^(id val) {
return [NSValue valueWithCATransform3D:[RCTConvert CATransform3D:val]];
},
@"@\"NSString\"": ^(id val) {
return [RCTConvert NSString:val];
},
@"@\"NSURL\"": ^(id val) {
return [RCTConvert NSURL:val];
},
@"@\"UIColor\"": ^(id val) {
return [RCTConvert UIColor:val];
},
@"@\"UIImage\"": ^(id val) {
return [RCTConvert UIImage:val];
},
@"@\"NSDate\"": ^(id val) {
return [RCTConvert NSDate:val];
},
@"@\"NSTimeZone\"": ^(id val) {
return [RCTConvert NSTimeZone:val];
},
};
});
// Handle null values
if (value == [NSNull null] && ![encoding isEqualToString:@"@\"NSNull\""]) {
return nil;
}
// Convert value
id (^converter)(id) = converters[encoding];
return converter ? converter(value) : value;
}
BOOL RCTSetProperty(id target, NSString *keypath, id value)
{
// Split keypath
NSArray *parts = [keypath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
target = [target valueForKey:parts[i]];
if (!target) {
return NO;
}
}
// Check target class for property definition
NSString *encoding = nil;
objc_property_t property = class_getProperty([target class], [key UTF8String]);
if (property) {
// Get type info
char *typeEncoding = property_copyAttributeValue(property, "T");
encoding = @(typeEncoding);
free(typeEncoding);
} else {
// Check if setter exists
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
if (![target respondsToSelector:setter]) {
return NO;
}
// Get type of first method argument
Method method = class_getInstanceMethod([target class], setter);
char *typeEncoding = method_copyArgumentType(method, 2);
if (typeEncoding) {
encoding = @(typeEncoding);
free(typeEncoding);
}
}
if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) {
// Not enough info about the type encoding to be useful, so
// try to guess the type from the value and property name
encoding = RCTGuessTypeEncoding(target, key, value, encoding);
}
// Special case for numeric encodings, which may be enums
if ([value isKindOfClass:[NSString class]] &&
[@"iIsSlLqQ" containsString:[encoding substringToIndex:1]]) {
/**
* NOTE: the property names below may seem weird, but it's
* because they are tested as case-sensitive suffixes, so
* "apitalizationType" will match any of the following
*
* - capitalizationType
* - autocapitalizationType
* - autoCapitalizationType
* - titleCapitalizationType
* - etc.
*/
static NSDictionary *converters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
converters =
@{
@"apitalizationType": ^(id val) {
return [RCTConvert UITextAutocapitalizationType:val];
},
@"eyboardType": ^(id val) {
return [RCTConvert UIKeyboardType:val];
},
@"extAlignment": ^(id val) {
return [RCTConvert NSTextAlignment:val];
},
};
});
for (NSString *subkey in converters) {
if ([key hasSuffix:subkey]) {
NSInteger (^converter)(NSString *) = converters[subkey];
value = @(converter(value));
break;
}
}
}
// Another nasty special case
if ([target isKindOfClass:[UITextField class]]) {
static NSDictionary *specialCases = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
specialCases = @{
@"autocapitalizationType": ^(UITextField *f, NSInteger v){ f.autocapitalizationType = v; },
@"autocorrectionType": ^(UITextField *f, NSInteger v){ f.autocorrectionType = v; },
@"spellCheckingType": ^(UITextField *f, NSInteger v){ f.spellCheckingType = v; },
@"keyboardType": ^(UITextField *f, NSInteger v){ f.keyboardType = v; },
@"keyboardAppearance": ^(UITextField *f, NSInteger v){ f.keyboardAppearance = v; },
@"returnKeyType": ^(UITextField *f, NSInteger v){ f.returnKeyType = v; },
@"enablesReturnKeyAutomatically": ^(UITextField *f, NSInteger v){ f.enablesReturnKeyAutomatically = !!v; },
@"secureTextEntry": ^(UITextField *f, NSInteger v){ f.secureTextEntry = !!v; }};
});
void (^block)(UITextField *f, NSInteger v) = specialCases[key];
if (block)
{
block(target, [value integerValue]);
return YES;
}
}
// Set converted value
[target setValue:RCTConvertValue(value, encoding) forKey:key];
return YES;
}
BOOL RCTCopyProperty(id target, id source, NSString *keypath)
{
// Split keypath
NSArray *parts = [keypath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
source = [source valueForKey:parts[i]];
target = [target valueForKey:parts[i]];
if (!source || !target) {
return NO;
}
}
// Check class for property definition
if (!class_getProperty([source class], [key UTF8String])) {
// Check if setter exists
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
if (![source respondsToSelector:setter]
|| ![target respondsToSelector:setter]) {
return NO;
}
}
[target setValue:[source valueForKey:key] forKey:key];
return YES;
}
BOOL RCTCallSetter(id target, SEL setter, id value)
{
// Get property name
NSString *propertyName = NSStringFromSelector(setter);
RCTCAssert([propertyName hasPrefix:@"set"] && [propertyName hasSuffix:@":"],
@"%@ is not a valid setter name", propertyName);
propertyName = [[[propertyName substringWithRange:(NSRange){3,1}] lowercaseString] stringByAppendingString:[propertyName substringWithRange:(NSRange){4,propertyName.length - 5}]];
// Set property
return RCTSetProperty(target, propertyName, value);
}

View File

@ -1,23 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"
@class RCTBridge;
/**
* Class that allows easy embedding, loading, life-cycle management of a
* JavaScript application inside of a native application.
* TODO: Before loading new application source, publish global notification in
* JavaScript so that applications can clean up resources. (launch blocker).
* TODO: Incremental module loading. (low pri).
*/
@interface RCTJavaScriptAppEngine : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete;
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL;
@end

View File

@ -1,158 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTJavaScriptAppEngine.h"
#import "RCTBridge.h"
#import "RCTInvalidating.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTUtils.h"
#define JS_SERVER_NOT_AVAILABLE @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root"
#define CACHE_DIR @"RCTJSBundleCache"
#pragma mark - Application Engine
/**
* TODO:
* - Add window resize rotation events matching the DOM API.
* - Device pixel ration hooks.
* - Source maps.
*/
@implementation RCTJavaScriptAppEngine
{
NSDictionary *_loadedResource;
__weak RCTBridge *_bridge;
}
- (instancetype)init
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
/**
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
* engine in its own dedicated thread.
*
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
* additional GCD dispatch per frame and likely makes it so that other UIThread
* operations don't delay the dispatch (so we can begin working in JS much
* faster.) Event handling must still be sent via a GCD dispatch, of course.
*
* We must add the display link to two runloops in order to get setTimeouts to
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
* TODO: We can invent a `requestAnimationFrame` and
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
* an animation.
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
*
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
if ((self = [super init])) {
_bridge = bridge;
}
return self;
}
#pragma mark - Module and script loading
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL
{
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *fileDir = [rootPath stringByAppendingPathComponent:CACHE_DIR];
NSString *filePath = [fileDir stringByAppendingPathComponent:RCTMD5Hash(moduleURL.absoluteString)];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
}
/**
* TODO: All loading of script via network or disk should be done in a separate
* thread, not the JS thread, and not the main UI thread (launch blocker).
*/
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
NSString *cachedFilePath;
if (useCache) {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *fileDir = [rootPath stringByAppendingPathComponent:CACHE_DIR];
cachedFilePath = [fileDir stringByAppendingPathComponent:RCTMD5Hash(moduleURL.absoluteString)];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachedFilePath]) {
NSError *error;
NSString *rawText = [NSString stringWithContentsOfFile:cachedFilePath encoding:NSUTF8StringEncoding error:&error];
if (rawText.length == 0 || error != nil) {
if (onComplete) onComplete(error);
} else {
[self _enqueueLoadBundleResource:rawText url:moduleURL onComplete:onComplete];
}
return;
}
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:moduleURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: JS_SERVER_NOT_AVAILABLE,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
if (onComplete) onComplete(error);
return;
}
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": @[@{
@"methodName": errorDetails[@"description"] ?: @"",
@"file": errorDetails[@"filename"] ?: @"",
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
}]
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
NSError *serverError = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
if (onComplete) onComplete(serverError);
return;
}
if (useCache) {
[[NSFileManager defaultManager] createDirectoryAtPath:cachedFilePath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:NULL];
[rawText writeToFile:cachedFilePath atomically:YES encoding:encoding error:NULL];
}
[self _enqueueLoadBundleResource:rawText url:moduleURL onComplete:onComplete];
}];
[task resume];
}
- (void)_enqueueLoadBundleResource:(NSString *)rawText url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
[_bridge enqueueApplicationScript:rawText url:url onComplete:onComplete];
}
@end

View File

@ -20,28 +20,28 @@
// 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; \
#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) { \
__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]; \
} \
[__RCTLog__redBox showErrorMessage:__RCTLog__msg]; \
} \
} while (0)
#define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
@ -53,71 +53,13 @@
#define RCTLogFormat(...) _RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
#define RCTLogFormatString(...) _RCTLogFormatString(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName) {
NSString *threadName = [[NSThread currentThread] name];
NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent];
if (!threadName || threadName.length <= 0) {
threadName = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
}
return [NSString stringWithFormat:@"[RCTLog][tid:%@][%@:%d]>", threadName, fileName, lineNumber];
}
// 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.
static inline NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
static inline NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
{
va_list args;
va_start(args, format);
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
BOOL valid = YES;
for (int i = 0; i < formatParts.count; i++) {
if (i == 0) { // first part is always a string
[objects addObject:formatParts[i]];
} else {
if (valid && [formatParts[i] length] && [formatParts[i] characterAtIndex:0] == '@') {
id obj = va_arg(args, id);
if (obj) {
[objects addObject:obj];
} else {
[objects addObject:@"null"];
}
[objects addObject:[formatParts[i] substringFromIndex:1]]; // remove formatting char
} else {
// We could determine the type (double, int?) of the va_arg by parsing the formatPart, but for now we just bail.
valid = NO;
[objects addObject:[NSString stringWithFormat:@"unknown object for %%%@", formatParts[i]]];
}
}
}
va_end(args);
va_start(args, format);
NSString *strOut = [preamble stringByAppendingString:[[NSString alloc] initWithFormat:format arguments:args]];
va_end(args);
NSMutableArray *objectsOut = [NSMutableArray arrayWithObject:strOut];
[objectsOut addObjectsFromArray:objects];
return objectsOut;
}
static inline NSString *_RCTLogFormatString(const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
static inline 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];
}
#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);
#ifdef __cplusplus
}

View File

@ -10,6 +10,16 @@ void RCTInjectLogFunction(RCTLogFunction func) {
injectedLogFunction = func;
}
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName)
{
NSString *threadName = [[NSThread currentThread] name];
NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent];
if (!threadName || threadName.length <= 0) {
threadName = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
}
return [NSString stringWithFormat:@"[RCTLog][tid:%@][%@:%d]>", threadName, fileName, lineNumber];
}
// TODO (#5906496): // kinda ugly that this is tied to RCTBridge
NSString *_RCTLogObjects(NSArray *objects, NSString *level)
{
@ -31,3 +41,48 @@ 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, ...)
{
va_list args;
va_start(args, format);
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
BOOL valid = YES;
for (int i = 0; i < formatParts.count; i++) {
if (i == 0) { // first part is always a string
[objects addObject:formatParts[i]];
} else {
if (valid && [formatParts[i] length] && [formatParts[i] characterAtIndex:0] == '@') {
id obj = va_arg(args, id);
[objects addObject:obj ?: @"null"];
[objects addObject:[formatParts[i] substringFromIndex:1]]; // remove formatting char
} else {
// We could determine the type (double, int?) of the va_arg by parsing the formatPart, but for now we just bail.
valid = NO;
[objects addObject:[NSString stringWithFormat:@"unknown object for %%%@", formatParts[i]]];
}
}
}
va_end(args);
va_start(args, format);
NSString *strOut = [preamble stringByAppendingString:[[NSString alloc] initWithFormat:format arguments:args]];
va_end(args);
NSMutableArray *objectsOut = [NSMutableArray arrayWithObject:strOut];
[objectsOut addObjectsFromArray:objects];
return objectsOut;
}
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];
}

View File

@ -1,19 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
@protocol RCTNativeModule;
@interface RCTModuleMethod : NSObject
- (instancetype)initWithSelector:(SEL)selector
JSMethodName:(NSString *)JSMethodName
arity:(NSUInteger)arity
blockArgumentIndexes:(NSIndexSet *)blockArgumentIndexes;
@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

View File

@ -1,35 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTModuleMethod.h"
@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

View File

@ -0,0 +1,10 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTPointerEvents) {
RCTPointerEventsUnspecified = 0, // Default
RCTPointerEventsNone,
RCTPointerEventsBoxNone,
RCTPointerEventsBoxOnly,
};

View File

@ -2,14 +2,35 @@
#import <UIKit/UIKit.h>
@protocol RCTJavaScriptExecutor;
@interface RCTRootView : UIView
/**
* The URL of the bundled application script (required).
* Setting this will clear the view contents, and trigger
* an asynchronous load/download and execution of the script.
*/
@property (nonatomic, strong) NSURL *scriptURL;
/**
* The name of the JavaScript module to execute within the
* specified scriptURL (required). Setting this will not have
* any immediate effect, but it must be done prior to loading
* the script.
*/
@property (nonatomic, copy) NSString *moduleName;
/**
* The default properties to apply to the view when the script bundle
* is first loaded. Defaults to nil/empty.
*/
@property (nonatomic, copy) NSDictionary *initialProperties;
@property (nonatomic, strong) id<RCTJavaScriptExecutor> executor;
/**
* The class of the RCTJavaScriptExecutor to use with this view.
* If not specified, it will default to using RCTContextExecutor.
* Changes will take effect next time the bundle is reloaded.
*/
@property (nonatomic, strong) Class executorClass;
/**
* Reload this root view, or all root views, respectively.

View File

@ -5,25 +5,25 @@
#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h"
#import "RCTJavaScriptAppEngine.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTViewManager.h"
#import "RCTWebViewExecutor.h"
#import "UIView+ReactKit.h"
#import "RCTKeyCommands.h"
NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification";
@implementation RCTRootView
{
RCTBridge *_bridge;
RCTJavaScriptAppEngine *_appEngine;
RCTTouchHandler *_touchHandler;
id <RCTJavaScriptExecutor> _executor;
}
static BOOL _useWebExec;
static Class _globalExecutorClass;
+ (void)initialize
{
@ -36,11 +36,12 @@ static BOOL _useWebExec;
action:^(UIKeyCommand *command) {
[self reloadAll];
}];
// Cmd-D reloads using the web view executor, allows attaching from Safari dev tools.
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_useWebExec = YES;
_globalExecutorClass = [RCTWebViewExecutor class];
[self reloadAll];
}];
@ -50,21 +51,18 @@ static BOOL _useWebExec;
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) return nil;
[self setUp];
if ((self = [super initWithCoder:aDecoder])) {
[self setUp];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) return nil;
[self setUp];
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = [UIColor whiteColor];
[self setUp];
}
return self;
}
@ -81,7 +79,6 @@ static BOOL _useWebExec;
selector:@selector(reload)
name:RCTRootViewReloadNotification
object:nil];
self.backgroundColor = [UIColor whiteColor];
}
- (void)dealloc
@ -104,10 +101,9 @@ static BOOL _useWebExec;
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": self.reactTag ?: @0,
@"rootTag": self.reactTag,
@"initialProps": self.initialProperties ?: @{},
};
[_bridge enqueueJSCall:@"Bundler.runApplication"
args:@[moduleName, appParameters]];
}
@ -122,28 +118,81 @@ static BOOL _useWebExec;
return;
}
__weak typeof(self) weakSelf = self;
RCTJavaScriptCompleteBlock callback = ^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf bundleFinishedLoading:error];
});
};
// Clean up
[self removeGestureRecognizer:_touchHandler];
[_executor invalidate];
[_bridge invalidate];
if (!_useWebExec) {
_executor = [[RCTContextExecutor alloc] init];
} else {
_executor = [[RCTWebViewExecutor alloc] init];
}
// Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self addGestureRecognizer:_touchHandler];
_appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge];
_touchHandler = [[RCTTouchHandler alloc] initWithEventDispatcher:_bridge.eventDispatcher rootView:self];
// Load the bundle
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:_scriptURL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root",
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
[self bundleFinishedLoading:error];
return;
}
// Parse response as text
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": @[@{
@"methodName": errorDetails[@"description"] ?: @"",
@"file": errorDetails[@"filename"] ?: @"",
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
}]
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
[self bundleFinishedLoading:error];
return;
}
[_appEngine loadBundleAtURL:_scriptURL useCache:NO onComplete:callback];
// Success!
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self bundleFinishedLoading:error];
});
}];
}];
[task resume];
}
- (void)setScriptURL:(NSURL *)scriptURL
@ -156,12 +205,6 @@ static BOOL _useWebExec;
[self loadBundle];
}
- (void)setExecutor:(id<RCTJavaScriptExecutor>)executor
{
RCTAssert(!_bridge, @"You may only change the Javascript Executor prior to loading a script bundle.");
_executor = executor;
}
- (BOOL)isReactRootView
{
return YES;
@ -169,7 +212,6 @@ static BOOL _useWebExec;
- (void)reload
{
[RCTJavaScriptAppEngine resetCacheForBundleAtURL:_scriptURL];
[self loadBundle];
}

View File

@ -2,11 +2,10 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@class RCTBridge;
@interface RCTTouchHandler : UIGestureRecognizer
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
rootView:(UIView *)rootView;
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@end

View File

@ -5,7 +5,7 @@
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "RCTAssert.h"
#import "RCTEventDispatcher.h"
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
@ -16,8 +16,7 @@
@implementation RCTTouchHandler
{
__weak UIView *_rootView;
RCTEventDispatcher *_eventDispatcher;
__weak RCTBridge *_bridge;
/**
* Arrays managed in parallel tracking native touch object along with the
@ -35,16 +34,13 @@
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
rootView:(UIView *)rootView
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithTarget:nil action:NULL])) {
RCTAssert(eventDispatcher != nil, @"Expect an event dispatcher");
RCTAssert(rootView != nil, @"Expect a root view");
RCTAssert(bridge != nil, @"Expect an event dispatcher");
_eventDispatcher = eventDispatcher;
_rootView = rootView;
_bridge = bridge;
_nativeTouches = [[NSMutableOrderedSet alloc] init];
_reactTouches = [[NSMutableArray alloc] init];
@ -53,11 +49,17 @@
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
// level components not build using RCT, will fail to recognize gestures.
self.cancelsTouchesInView = NO;
[_rootView addGestureRecognizer:self];
}
return self;
}
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
RCTTouchEventTypeStart,
RCTTouchEventTypeMove,
RCTTouchEventTypeEnd,
RCTTouchEventTypeCancel
};
#pragma mark - Bookkeeping for touch indices
- (void)_recordNewTouches:(NSSet *)touches
@ -122,7 +124,7 @@
{
UITouch *nativeTouch = _nativeTouches[touchIndex];
CGPoint windowLocation = [nativeTouch locationInView:nativeTouch.window];
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:_rootView];
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:self.view];
UIView *touchView = _touchViews[touchIndex];
CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView];
@ -135,15 +137,26 @@
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
}
- (void)_updateAndDispatchTouches:(NSSet *)touches eventType:(RCTTouchEventType)eventType
/**
* Constructs information about touch events to send across the serialized
* boundary. This data should be compliant with W3C `Touch` objects. This data
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
* there must be a simple receiver on the other side of the bridge that
* organizes the touch objects into `Event`s.
*
* We send the data as an array of `Touch`es, the type of action
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
* from that array.
*/
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName
{
// Update touches
NSMutableArray *changedIndices = [[NSMutableArray alloc] init];
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
RCTAssert(index != NSNotFound, @"Touch not found. This is a critical bug.");
[self _updateReactTouchAtIndex:index];
[changedIndices addObject:@(index)];
[changedIndexes addObject:@(index)];
}
// Deep copy the touches because they will be accessed from another thread
@ -154,9 +167,8 @@
}
// Dispatch touch event
[_eventDispatcher sendTouchEventWithType:eventType
touches:reactTouches
changedIndexes:changedIndices];
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
args:@[eventName, reactTouches, changedIndexes]];
}
#pragma mark - Gesture Recognizer Delegate Callbacks
@ -169,7 +181,7 @@
// "start" has to record new touches before extracting the event.
// "end"/"cancel" needs to remove the touch *after* extracting the event.
[self _recordNewTouches:touches];
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeStart];
[self _updateAndDispatchTouches:touches eventName:@"topTouchStart"];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
@ -178,20 +190,20 @@
if (self.state == UIGestureRecognizerStateFailed) {
return;
}
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeMove];
[self _updateAndDispatchTouches:touches eventName:@"topTouchMove"];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeEnd];
[self _updateAndDispatchTouches:touches eventName:@"topTouchEnd"];
[self _recordRemovedTouches:touches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventType:RCTTouchEventTypeCancel];
[self _updateAndDispatchTouches:touches eventName:@"topTouchCancel"];
[self _recordRemovedTouches:touches];
}

View File

@ -17,7 +17,7 @@ do { \
NSString *RCTJSONStringify(id jsonObject, NSError **error);
id RCTJSONParse(NSString *jsonString, NSError **error);
// Get MD5 hash of a string
// Get MD5 hash of a string (TODO: currently unused. Remove?)
NSString *RCTMD5Hash(NSString *string);
// Get screen metrics in a thread-safe way

View File

@ -22,8 +22,4 @@
// TODO: Deprecate this
- (void)reactBridgeDidFinishTransaction;
// Invoked when react determines that the view will be removed from the view
// hierarchy and never replaced.
- (void)reactWillDestroy;
@end

View File

@ -13,15 +13,16 @@
* environment. And ensure main thread operations are actually added to a queue
* instead of being executed immediately if already on the main thread.
*/
@interface RCTWebViewExecutor : NSObject<RCTJavaScriptExecutor, UIWebViewDelegate>
@property (nonatomic, readwrite, strong) UIWebView *webView;
@interface RCTWebViewExecutor : NSObject<RCTJavaScriptExecutor>
// Only one callback stored - will only be invoked for the latest issued
// application script request.
@property (nonatomic, readwrite, copy) RCTJavaScriptCompleteBlock onApplicationScriptLoaded;
@property (nonatomic, copy) RCTJavaScriptCompleteBlock onApplicationScriptLoaded;
- (instancetype)initWithWebView:(UIWebView *)webView;
/**
* Instantiate with a specific webview instance
*/
- (instancetype)initWithWebView:(UIWebView *)webView NS_DESIGNATED_INITIALIZER;
/**
* Invoke this to reclaim the web view for reuse. This is necessary in order to

View File

@ -23,8 +23,13 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
va_end(args);
}
@interface RCTWebViewExecutor () <UIWebViewDelegate>
@end
@implementation RCTWebViewExecutor
{
UIWebView *_webView;
NSMutableDictionary *_objectsToInject;
}

View File

@ -2,8 +2,8 @@
#import <UIKit/UIKit.h>
#import "RCTExport.h"
#import "RCTBridgeModule.h"
@interface RCTAlertManager : NSObject <RCTNativeModule>
@interface RCTAlertManager : NSObject <RCTBridgeModule>
@end

View File

@ -2,9 +2,9 @@
#import <Foundation/Foundation.h>
#import "RCTExport.h"
#import "RCTBridgeModule.h"
@interface RCTDataManager : NSObject <RCTNativeModule>
@interface RCTDataManager : NSObject <RCTBridgeModule>
@end

View File

@ -2,8 +2,8 @@
#import <Foundation/Foundation.h>
#import "RCTExport.h"
#import "RCTBridgeModule.h"
@interface RCTExceptionsManager : NSObject <RCTNativeModule>
@interface RCTExceptionsManager : NSObject <RCTBridgeModule>
@end

View File

@ -2,13 +2,9 @@
#import <Foundation/Foundation.h>
#import "RCTExport.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
@class RCTBridge;
@interface RCTTiming : NSObject <RCTNativeModule, RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating>
@end

View File

@ -2,19 +2,16 @@
#import <UIKit/UIKit.h>
#import "RCTExport.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
@class RCTBridge;
@class RCTRootView;
@class RCTShadowView;
@class RCTSparseArray;
@protocol RCTScrollableProtocol;
@protocol RCTViewNodeProtocol;
@interface RCTUIManager : NSObject <RCTNativeModule, RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@interface RCTUIManager : NSObject <RCTBridgeModule, RCTInvalidating>
@property (nonatomic, strong) RCTSparseArray *shadowViewRegistry;
@property (nonatomic, strong) RCTSparseArray *viewRegistry;
@ -26,9 +23,6 @@
*/
@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
+ (UIView <RCTViewNodeProtocol> *)closestReactAncestor:(UIView *)view;
+ (UIView <RCTViewNodeProtocol> *)closestReactAncestorThatRespondsToTouch:(UITouch *)touch;
- (void)registerRootView:(RCTRootView *)rootView;
+ (UIView *)JSResponder;

View File

@ -19,7 +19,7 @@
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTViewNodeProtocol.h"
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
#import "UIView+ReactKit.h"
@class RCTAnimationConfig;
@ -52,17 +52,20 @@ static NSDictionary *RCTViewModuleClasses(void)
continue;
}
if (![cls conformsToProtocol:@protocol(RCTNativeViewModule)]) {
// Not an RCTNativeModule
if (![cls isSubclassOfClass:[RCTViewManager class]]) {
// Not a view module
continue;
}
// Get module name
NSString *moduleName = [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
NSString *moduleName = [cls moduleName];
// Check module name is unique
id existingClass = modules[moduleName];
RCTCAssert(existingClass == Nil, @"Attempted to register RCTNativeViewModule class %@ for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
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;
}
@ -90,14 +93,13 @@ static NSDictionary *RCTViewModuleClasses(void)
__weak RCTBridge *_bridge;
}
- (id <RCTNativeViewModule>)_managerInstanceForViewWithModuleName:(NSString *)moduleName
- (RCTViewManager *)_managerInstanceForViewWithModuleName:(NSString *)moduleName
{
id <RCTNativeViewModule> managerInstance = _viewManagers[moduleName];
RCTViewManager *managerInstance = _viewManagers[moduleName];
if (managerInstance == nil) {
RCTLogWarn(@"No manager class found for view with module name \"%@\"", moduleName);
managerInstance = [[RCTUIViewManager alloc] init];
managerInstance = [[RCTViewManager alloc] init];
}
return managerInstance;
}
@ -183,27 +185,6 @@ static NSDictionary *RCTViewModuleClasses(void)
});
}
+ (UIView *)closestReactAncestor:(UIView *)view
{
UIView *currentUIView = view;
while (currentUIView && !currentUIView.reactTag) {
currentUIView = currentUIView.superview;
}
return (UIView *)currentUIView;
}
+ (UIView *)closestReactAncestorThatRespondsToTouch:(UITouch *)touch
{
UIView *currentUIView = [RCTUIManager closestReactAncestor:touch.view];
while (currentUIView != nil) {
if ([currentUIView isUserInteractionEnabled]) { // TODO: implement respondsToTouch:touch mechanism
return currentUIView;
}
currentUIView = [RCTUIManager closestReactAncestor:currentUIView.superview];
}
return nil;
}
/**
* Unregisters views from registries
*/
@ -212,8 +193,8 @@ static NSDictionary *RCTViewModuleClasses(void)
for (id<RCTViewNodeProtocol> child in children) {
RCTTraverseViewNodes(registry[child.reactTag], ^(id<RCTViewNodeProtocol> subview) {
RCTAssert(![subview isReactRootView], @"Host views should not be unregistered");
if ([subview respondsToSelector:@selector(reactWillDestroy)]) {
[subview reactWillDestroy];
if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
[(id<RCTInvalidating>)subview invalidate];
}
registry[subview.reactTag] = nil;
});
@ -332,17 +313,10 @@ static NSDictionary *RCTViewModuleClasses(void)
{
NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1];
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
NSMutableArray *propsAppliers = [NSMutableArray arrayWithCapacity:applierBlocks.count];
for (RCTApplierBlock propsApplier in applierBlocks) {
[propsAppliers addObject:propsApplier];
}
NSArray *immutablePropsAppliers = propsAppliers;
[self addUIBlock:^(RCTUIManager *viewManager, RCTSparseArray *viewRegistry) {
for (NSUInteger f = 0; f < immutablePropsAppliers.count; f++) {
RCTApplierBlock applier = [immutablePropsAppliers objectAtIndex: f];
applier(viewRegistry);
for (RCTApplierBlock block in applierBlocks) {
block(viewRegistry);
}
}];
}
@ -515,7 +489,7 @@ static NSDictionary *RCTViewModuleClasses(void)
}
}
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id <RCTNativeViewModule>manager)
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, RCTViewManager *manager)
{
// TODO: cache respondsToSelector tests
if ([manager respondsToSelector:setter]) {
@ -531,7 +505,7 @@ static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView,
}
static void RCTSetViewProps(NSDictionary *props, UIView *view,
UIView *defaultView, id <RCTNativeViewModule>manager)
UIView *defaultView, RCTViewManager *manager)
{
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
@ -544,7 +518,7 @@ static void RCTSetViewProps(NSDictionary *props, UIView *view,
}
static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView,
RCTShadowView *defaultView, id <RCTNativeViewModule>manager)
RCTShadowView *defaultView, RCTViewManager *manager)
{
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
@ -575,14 +549,14 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
{
RCT_EXPORT(createView);
id <RCTNativeViewModule>manager = [self _managerInstanceForViewWithModuleName:moduleName];
RCTViewManager *manager = [self _managerInstanceForViewWithModuleName:moduleName];
// Generate default view, used for resetting default props
if (!_defaultShadowViews[moduleName]) {
_defaultShadowViews[moduleName] = ([manager respondsToSelector:@selector(shadowView)] ? [manager shadowView] : nil) ?: [[RCTShadowView alloc] init];
_defaultShadowViews[moduleName] = [manager shadowView];
}
RCTShadowView *shadowView = ([manager respondsToSelector:@selector(shadowView)] ? [manager shadowView] : nil) ?: [[RCTShadowView alloc] init];
RCTShadowView *shadowView = [manager shadowView];
shadowView.moduleName = moduleName;
shadowView.reactTag = reactTag;
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], manager);
@ -619,7 +593,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCT_EXPORT();
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
id <RCTNativeViewModule>manager = [self _managerInstanceForViewWithModuleName:moduleName];
RCTViewManager *manager = [self _managerInstanceForViewWithModuleName:moduleName];
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], manager);
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
@ -657,8 +631,8 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
// pending blocks to a new array. This guards against mutation while
// processing the pending blocks in another thread.
for (id <RCTNativeViewModule>viewManager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [viewManager respondsToSelector:@selector(uiBlockToAmendWithShadowViewRegistry:)] ? [viewManager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry] : nil;
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
if (uiBlock != nil) {
[self addUIBlock:uiBlock];
}

View File

@ -13,7 +13,6 @@
134FCB371A6D4ED700051CC8 /* RCTRawTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137029581A6C197000575408 /* RCTRawTextManager.m */; };
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; };
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; };
137029331A69659C00575408 /* RCTExport.m in Sources */ = {isa = PBXBuildFile; fileRef = 830213F41A65574D00B993E6 /* RCTExport.m */; };
137029491A698FF000575408 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137029401A698FF000575408 /* RCTNetworkImageViewManager.m */; };
137029501A6990A100575408 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1370294F1A6990A100575408 /* RCTNetworkImageView.m */; };
137029531A69923600575408 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 137029521A69923600575408 /* RCTImageDownloader.m */; };
@ -37,9 +36,8 @@
13B080291A694C4900A75B9A /* RCTDataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080281A694C4900A75B9A /* RCTDataManager.m */; };
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; };
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; };
13E067561A70F44B002CDEE1 /* RCTUIViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTUIViewManager.m */; };
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067581A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067521A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */; };
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
@ -49,10 +47,8 @@
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA591A601E9000E9B192 /* RCTRedBox.m */; };
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */; };
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; };
83CBBA871A60202500E9B192 /* RCTJavaScriptAppEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */; };
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; };
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; };
83EEC2EE1A604AB200C39218 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -128,16 +124,14 @@
13E067491A70F434002CDEE1 /* RCTUIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManager.m; sourceTree = "<group>"; };
13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowView.h; sourceTree = "<group>"; };
13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowView.m; sourceTree = "<group>"; };
13E0674D1A70F44B002CDEE1 /* RCTUIViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIViewManager.h; sourceTree = "<group>"; };
13E0674E1A70F44B002CDEE1 /* RCTUIViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIViewManager.m; sourceTree = "<group>"; };
13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewManager.h; sourceTree = "<group>"; };
13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTViewManager.m; sourceTree = "<group>"; };
13E0674F1A70F44B002CDEE1 /* RCTView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTView.h; sourceTree = "<group>"; };
13E067501A70F44B002CDEE1 /* RCTView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTView.m; sourceTree = "<group>"; };
13E067511A70F44B002CDEE1 /* RCTViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewManager.h; sourceTree = "<group>"; };
13E067521A70F44B002CDEE1 /* RCTViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTViewManager.m; sourceTree = "<group>"; };
13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ReactKit.h"; sourceTree = "<group>"; };
13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ReactKit.m"; sourceTree = "<group>"; };
830213F31A654E0800B993E6 /* RCTExport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTExport.h; sourceTree = "<group>"; };
830213F41A65574D00B993E6 /* RCTExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTExport.m; sourceTree = "<group>"; };
13ED13891A80C9D40050A8F9 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = "<group>"; };
@ -159,14 +153,10 @@
83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptExecutor.h; sourceTree = "<group>"; };
83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTEventDispatcher.h; sourceTree = "<group>"; };
83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcher.m; sourceTree = "<group>"; };
83CBBA851A60202500E9B192 /* RCTJavaScriptAppEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptAppEngine.h; sourceTree = "<group>"; };
83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptAppEngine.m; sourceTree = "<group>"; };
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchHandler.h; sourceTree = "<group>"; };
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = "<group>"; };
83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = "<group>"; };
83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = "<group>"; };
83EEC2EC1A604AB200C39218 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = "<group>"; };
83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -224,12 +214,10 @@
13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */,
13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */,
13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */,
13E0674D1A70F44B002CDEE1 /* RCTUIViewManager.h */,
13E0674E1A70F44B002CDEE1 /* RCTUIViewManager.m */,
13E0674F1A70F44B002CDEE1 /* RCTView.h */,
13E067501A70F44B002CDEE1 /* RCTView.m */,
13E067511A70F44B002CDEE1 /* RCTViewManager.h */,
13E067521A70F44B002CDEE1 /* RCTViewManager.m */,
13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */,
13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */,
13B07FF61A6947C200A75B9A /* RCTScrollView.h */,
13B07FF71A6947C200A75B9A /* RCTScrollView.m */,
13B07FF81A6947C200A75B9A /* RCTScrollViewManager.h */,
@ -308,17 +296,12 @@
83CBBA611A601EB200E9B192 /* RCTAutoInsetsProtocol.h */,
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */,
83EEC2EC1A604AB200C39218 /* RCTModuleMethod.h */,
83EEC2ED1A604AB200C39218 /* RCTModuleMethod.m */,
83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
830213F31A654E0800B993E6 /* RCTExport.h */,
830213F41A65574D00B993E6 /* RCTExport.m */,
830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
830A229C1A66C68A008503DA /* RCTRootView.h */,
830A229D1A66C68A008503DA /* RCTRootView.m */,
83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */,
83CBBA851A60202500E9B192 /* RCTJavaScriptAppEngine.h */,
83CBBA861A60202500E9B192 /* RCTJavaScriptAppEngine.m */,
83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */,
83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */,
83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */,
@ -336,6 +319,7 @@
137029511A69923600575408 /* RCTImageDownloader.h */,
137029521A69923600575408 /* RCTImageDownloader.m */,
13B07FCD1A683B5F00A75B9A /* RCTScrollableProtocol.h */,
13ED13891A80C9D40050A8F9 /* RCTPointerEvents.h */,
);
path = Base;
sourceTree = "<group>";
@ -424,7 +408,7 @@
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
832348161A77A5AA00B55238 /* Layout.c in Sources */,
13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */,
13E067561A70F44B002CDEE1 /* RCTUIViewManager.m in Sources */,
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */,
13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */,
@ -442,17 +426,13 @@
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */,
137029491A698FF000575408 /* RCTNetworkImageViewManager.m in Sources */,
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
13E067581A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
13B080081A6947C200A75B9A /* RCTShadowText.m in Sources */,
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */,
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
83CBBA871A60202500E9B192 /* RCTJavaScriptAppEngine.m in Sources */,
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
83EEC2EE1A604AB200C39218 /* RCTModuleMethod.m in Sources */,
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
137029331A69659C00575408 /* RCTExport.m in Sources */,
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
134FCB371A6D4ED700051CC8 /* RCTRawTextManager.m in Sources */,
13B0800B1A6947C200A75B9A /* RCTTextManager.m in Sources */,

View File

@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTNavItemManager : RCTUIViewManager
@interface RCTNavItemManager : RCTViewManager
@end

View File

@ -2,9 +2,11 @@
#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"
@class RCTEventDispatcher;
@interface RCTNavigator : UIView <UINavigationControllerDelegate>
@interface RCTNavigator : UIView <RCTInvalidating>
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;

View File

@ -200,7 +200,7 @@ NSInteger kNeverProgressed = -10000;
@end
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener>
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
{
RCTEventDispatcher *_eventDispatcher;
NSInteger _numberOfViewControllerMovesToIgnore;
@ -417,9 +417,14 @@ NSInteger kNeverProgressed = -10000;
return _currentViews;
}
- (void)reactWillDestroy
- (BOOL)isValid
{
// Removes run loop's references to `displayLink`.
return _displayLink != nil;
}
- (void)invalidate
{
// Prevent displayLink from retaining the navigator indefinitely
[_displayLink invalidate];
_displayLink = nil;
_runTimer = nil;

View File

@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTNavigatorManager : RCTUIViewManager
@interface RCTNavigatorManager : RCTViewManager
@end

View File

@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTNetworkImageViewManager : RCTUIViewManager
@interface RCTNetworkImageViewManager : RCTViewManager
@end

View File

@ -1,7 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTRawTextManager : RCTUIViewManager
@interface RCTRawTextManager : RCTViewManager
@end

View File

@ -242,11 +242,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
return frame;
}
- (void)insertReactSubview:(NSObject<RCTViewNodeProtocol> *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
}
@end
@implementation RCTScrollView

View File

@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTScrollViewManager : RCTUIViewManager
@interface RCTScrollViewManager : RCTViewManager
@end

View File

@ -1,7 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTStaticImageManager : RCTUIViewManager
@interface RCTStaticImageManager : RCTViewManager
@end

View File

@ -14,12 +14,13 @@
return [[RCTStaticImage alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(capInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
- (void)set_src:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
if (json) {
if ([json isKindOfClass:[NSString class]] && [[json pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
[view.layer addAnimation:[RCTConvert GIF:json] forKey:@"contents"];
} else {
view.image = [RCTConvert UIImage:json];
@ -29,11 +30,6 @@ RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
}
}
- (void)set_capInsets:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
view.capInsets = json ? [RCTConvert UIEdgeInsets:json] : defaultView.capInsets;
}
- (void)set_tintColor:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
if (json) {

View File

@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTTextFieldManager : RCTUIViewManager
@interface RCTTextFieldManager : RCTViewManager
@end

View File

@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTTextManager : RCTUIViewManager
@interface RCTTextManager : RCTViewManager
@end

View File

@ -67,6 +67,13 @@ RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment);
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
// TODO: the purpose of this block is effectively just to copy properties from the shadow views
// to their equivalent UIViews. In this case, the property being copied is the attributed text,
// but the same principle could be used to copy any property. The implementation is really ugly tho
// because the RCTViewManager doesn't retain a reference to the views that it manages, so it basically
// has to search the entire view hierarchy for relevant views. Not awesome. This seems like something
// where we could introduce a generic solution - perhaps a method on RCTShadowView that is called after
// layout to copy its properties across?
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
NSMutableArray *shadowBlocks = [NSMutableArray new];

View File

@ -1,7 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTUIActivityIndicatorViewManager : RCTUIViewManager
@interface RCTUIActivityIndicatorViewManager : RCTViewManager
@end

View File

@ -1,15 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import "RCTExport.h"
@class RCTEventDispatcher;
@interface RCTUIViewManager : NSObject <RCTNativeViewModule>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
@end

View File

@ -1,143 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTShadowView.h"
#import "RCTView.h"
@implementation RCTUIViewManager
{
__weak RCTEventDispatcher *_eventDispatcher;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_eventDispatcher = eventDispatcher;
}
return self;
}
+ (NSString *)moduleName
{
// Default implementation, works in most cases
NSString *name = NSStringFromClass(self);
if ([name hasPrefix:@"RCTUI"]) {
name = [name substringFromIndex:@"RCT".length];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
- (UIView *)view
{
return [[UIView alloc] init];
}
// View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel)
RCT_EXPORT_VIEW_PROPERTY(hidden)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier)
RCT_REMAP_VIEW_PROPERTY(opacity, alpha)
RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor);
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset);
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius)
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
{
view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds;
}
- (void)set_pointerEvents:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
if (!json) {
view.userInteractionEnabled = defaultView.userInteractionEnabled;
return;
}
switch ([RCTConvert NSInteger:json]) {
case RCTPointerEventsUnspecified:
// Pointer events "unspecified" acts as if a stylesheet had not specified,
// which is different than "auto" in CSS (which cannot and will not be
// supported in `ReactKit`. "auto" may override a parent's "none".
// Unspecified values do not.
// This wouldn't override a container view's `userInteractionEnabled = NO`
view.userInteractionEnabled = YES;
case RCTPointerEventsNone:
view.userInteractionEnabled = NO;
break;
default:
RCTLogError(@"UIView base class does not support pointerEvent value: %@", json);
}
}
// ShadowView properties
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
- (void)set_flexDirection:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection;
}
- (void)set_flexWrap:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap;
}
- (void)set_justifyContent:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent;
}
- (void)set_alignItems:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems;
}
- (void)set_alignSelf:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf;
}
- (void)set_position:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType;
}
@end

View File

@ -4,19 +4,13 @@
#import <UIKit/UIKit.h>
// TODO: rehome this
typedef NS_ENUM(NSInteger, RCTPointerEventsValue) {
RCTPointerEventsUnspecified = 0, // Default
RCTPointerEventsNone,
RCTPointerEventsBoxNone,
RCTPointerEventsBoxOnly,
};
#import "RCTPointerEvents.h"
@protocol RCTAutoInsetsProtocol;
@interface RCTView : UIView
@property (nonatomic, assign) RCTPointerEventsValue pointerEvents;
@property (nonatomic, assign) RCTPointerEvents pointerEvents;
@property (nonatomic, copy) NSString *overrideAccessibilityLabel;
+ (void)autoAdjustInsetsForView:(UIView<RCTAutoInsetsProtocol> *)parentView

View File

@ -40,7 +40,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
return RCTRecursiveAccessibilityLabel(self);
}
- (void)setPointerEvents:(RCTPointerEventsValue)pointerEvents
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
{
_pointerEvents = pointerEvents;
self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);

View File

@ -1,7 +1,148 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import <UIKit/UIKit.h>
@interface RCTViewManager : RCTUIViewManager
#import "RCTConvert.h"
#import "RCTLog.h"
@class RCTEventDispatcher;
@class RCTShadowView;
@class RCTSparseArray;
@class RCTUIManager;
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
@interface RCTViewManager : NSObject
/**
* Designated initializer for view modules. Override this when subclassing.
*/
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* 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;
/**
* The module name exposed to React JS. If omitted, this will be inferred
* automatically by using the view module's class name. It is better to not
* override this, and just follow standard naming conventions for your view
* module subclasses.
*/
+ (NSString *)moduleName;
/**
* This method instantiates a native view to be managed by the module. Override
* this to return a custom view instance, which may be preconfigured with default
* properties, subviews, etc. This method will be called many times, and should
* return a fresh instance each time. The view module MUST NOT cache the returned
* view and return the same instance for subsequent calls.
*/
- (UIView *)view;
/**
* This method instantiates a shadow view to be managed by the module. If omitted,
* an ordinary RCTShadowView instance will be created, which is typically fine for
* most view types. As with the -view method, the -shadowView method should return
* a fresh instance each time it is called.
*/
- (RCTShadowView *)shadowView;
/**
* Returns a dictionary of config data passed to JS that defines eligible events
* that can be placed on native views. This should return bubbling
* directly-dispatched event types and specify what names should be used to
* subscribe to either form (bubbling/capturing).
*
* Returned dictionary should be of the form: @{
* @"onTwirl": {
* @"phasedRegistrationNames": @{
* @"bubbled": @"onTwirl",
* @"captured": @"onTwirlCaptured"
* }
* }
* }
*
* Note that this method is not inherited when you subclass a view module, and
* you should not call [super customBubblingEventTypes] when overriding it.
*/
+ (NSDictionary *)customBubblingEventTypes;
/**
* Returns a dictionary of config data passed to JS that defines eligible events
* that can be placed on native views. This should return non-bubbling
* directly-dispatched event types.
*
* Returned dictionary should be of the form: @{
* @"onTwirl": {
* @"registrationName": @"onTwirl"
* }
* }
*
* Note that this method is not inherited when you subclass a view module, and
* you should not call [super customDirectEventTypes] when overriding it.
*/
+ (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.
*/
+ (NSDictionary *)constantsToExport;
/**
* To deprecate, 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_<propertyName>:(id)property
* forView:(UIView *)view
* withDefaultView:(UIView *)defaultView;
*
* - (void)set_<propertyName>:(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.
*/
#define RCT_EXPORT_VIEW_PROPERTY(name) \
RCT_REMAP_VIEW_PROPERTY(name, name)
/**
* This macro maps a named property on the module to an arbitrary key path
* within the view.
*/
#define RCT_REMAP_VIEW_PROPERTY(name, keypath) \
- (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); \
} \
}
/**
* These are useful in cases where the module's superclass handles a
* property, but you wish to "unhandle" it, so it will be ignored.
*/
#define RCT_IGNORE_VIEW_PROPERTY(name) \
- (void)set_##name:(id)value forView:(id)view withDefaultView:(id)defaultView {}
#define RCT_IGNORE_SHADOW_PROPERTY(name) \
- (void)set_##name:(id)value forShadowView:(id)view withDefaultView:(id)defaultView {}
@end

View File

@ -2,15 +2,169 @@
#import "RCTViewManager.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTShadowView.h"
#import "RCTView.h"
@implementation RCTViewManager
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_eventDispatcher = eventDispatcher;
}
return self;
}
+ (NSString *)moduleName
{
// Default implementation, works in most cases
NSString *name = NSStringFromClass(self);
if ([name hasPrefix:@"RCTUI"]) {
name = [name substringFromIndex:@"RCT".length];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
- (UIView *)view
{
return [[RCTView alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(pointerEvents)
- (RCTShadowView *)shadowView
{
return [[RCTShadowView alloc] init];
}
+ (NSDictionary *)customBubblingEventTypes
{
return nil;
}
+ (NSDictionary *)customDirectEventTypes
{
return nil;
}
+ (NSDictionary *)constantsToExport
{
return nil;
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
return nil;
}
// View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel)
RCT_EXPORT_VIEW_PROPERTY(hidden)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier)
RCT_REMAP_VIEW_PROPERTY(opacity, alpha)
RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor);
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset);
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius)
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, view.layer.transform)
- (void)set_overflow:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds;
}
- (void)set_pointerEvents:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
[(id)view setPointerEvents:json ? [RCTConvert RCTPointerEvents:json] : [(id)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,
// which is different than "auto" in CSS (which cannot and will not be
// supported in `ReactKit`. "auto" may override a parent's "none".
// Unspecified values do not.
// This wouldn't override a container view's `userInteractionEnabled = NO`
view.userInteractionEnabled = YES;
case RCTPointerEventsNone:
view.userInteractionEnabled = NO;
break;
default:
RCTLogError(@"UIView base class does not support pointerEvent value: %@", json);
}
}
// ShadowView properties
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
- (void)set_flexDirection:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection;
}
- (void)set_flexWrap:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap;
}
- (void)set_justifyContent:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent;
}
- (void)set_alignItems:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems;
}
- (void)set_alignSelf:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf;
}
- (void)set_position:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType;
}
@end