2015-02-02 updates

- Removed special-case treatment of RCTTiming and RCTUIManager modules | Nick Lockwood
This commit is contained in:
Christopher Chedeau 2015-02-03 16:12:07 -08:00
parent 00f0ebccdf
commit ccd8f184af
11 changed files with 340 additions and 360 deletions

View File

@ -92,27 +92,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 +120,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

@ -6,33 +6,8 @@
@protocol RCTNativeModule;
@class RCTUIManager;
@class RCTEventDispatcher;
/**
* 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`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
RCTBridgeFieldResponseCBIDs,
RCTBridgeFieldResponseReturnValues,
RCTBridgeFieldFlushDateMillis
};
@class RCTRootView;
/**
* Utilities for constructing common response objects. When sending a
@ -59,19 +34,19 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
@interface RCTBridge : NSObject <RCTInvalidating>
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
shadowQueue:(dispatch_queue_t)shadowQueue
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig;
- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args;
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
- (void)enqueueUpdateTimers;
@property (nonatomic, readonly) RCTUIManager *uiManager;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
// For use in implementing delegates, which may need to queue responses.
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID;
- (void)registerRootView:(RCTRootView *)rootView;
/**
* Global logging function will print to both xcode and js debugger consoles.
*

View File

@ -9,11 +9,34 @@
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTModuleIDs.h"
#import "RCTTiming.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
NSString *RCTModuleName(Class moduleClass)
/**
* 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`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
RCTBridgeFieldResponseCBIDs,
RCTBridgeFieldResponseReturnValues,
RCTBridgeFieldFlushDateMillis
};
static NSString *RCTModuleName(Class moduleClass)
{
if ([moduleClass respondsToSelector:@selector(moduleName)]) {
@ -22,22 +45,11 @@ NSString *RCTModuleName(Class moduleClass)
} else {
// Default implementation, works in most cases
NSString *className = NSStringFromClass(moduleClass);
// TODO: be more consistent with naming so that this check isn't needed
if ([moduleClass conformsToProtocol:@protocol(RCTNativeViewModule)]) {
if ([className hasPrefix:@"RCTUI"]) {
className = [className substringFromIndex:@"RCT".length];
}
if ([className hasSuffix:@"Manager"]) {
className = [className substringToIndex:className.length - @"Manager".length];
}
}
return className;
return NSStringFromClass(moduleClass);
}
}
NSDictionary *RCTNativeModuleClasses(void)
static NSDictionary *RCTNativeModuleClasses(void)
{
static NSMutableDictionary *modules;
static dispatch_once_t onceToken;
@ -79,46 +91,30 @@ NSDictionary *RCTNativeModuleClasses(void)
{
NSMutableDictionary *_moduleInstances;
NSDictionary *_javaScriptModulesConfig;
dispatch_queue_t _shadowQueue;
RCTTiming *_timing;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
shadowQueue:(dispatch_queue_t)shadowQueue
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig
{
if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor;
_latestJSExecutor = _javaScriptExecutor;
_shadowQueue = shadowQueue;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_moduleInstances = [[NSMutableDictionary alloc] init];
// TODO (#5906496): Remove special case
_timing = [[RCTTiming alloc] initWithBridge:self];
_javaScriptModulesConfig = javaScriptModulesConfig;
_moduleInstances[RCTModuleName([RCTTiming class])] = _timing;
// TODO (#5906496): Remove special case
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if ([moduleClass conformsToProtocol:@protocol(RCTNativeViewModule)]) {
viewManagers[moduleName] = [[moduleClass alloc] init];
}
}];
_uiManager = [[RCTUIManager alloc] initWithShadowQueue:_shadowQueue viewManagers:viewManagers];
_uiManager.eventDispatcher = _eventDispatcher;
_moduleInstances[RCTModuleName([RCTUIManager class])] = _uiManager;
[_moduleInstances addEntriesFromDictionary:viewManagers];
// Register remaining modules
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Register modules
_moduleInstances = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) {
_moduleInstances[moduleName] = [[moduleClass alloc] init];
if ([moduleClass instancesRespondToSelector:@selector(initWithBridge:)]) {
_moduleInstances[moduleName] = [[moduleClass alloc] initWithBridge:self];
} else {
_moduleInstances[moduleName] = [[moduleClass alloc] init];
}
}
}];
@ -148,18 +144,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_javaScriptExecutor = nil;
dispatch_sync(_shadowQueue, ^{
// Make sure all dispatchers have been executed before
// freeing up memory from _asyncHookMapByModuleID
// Make sure all dispatchers have been executed before continuing
// TODO: is this still needed?
});
for (id target in _moduleInstances.objectEnumerator) {
if ([target respondsToSelector:@selector(invalidate)]) {
[(id<RCTInvalidating>)target invalidate];
}
}
[_moduleInstances removeAllObjects];
_timing = nil;
}
/**
@ -199,11 +193,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}];
}
- (void)enqueueUpdateTimers
{
[_timing enqueueUpdateTimers];
}
#pragma mark - Payload Generation
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
@ -274,12 +263,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
NSArray *moduleIDs = [requestsArray objectAtIndex:RCTBridgeFieldRequestModuleIDs];
NSArray *methodIDs = [requestsArray objectAtIndex:RCTBridgeFieldMethodIDs];
NSArray *paramss = [requestsArray objectAtIndex:RCTBridgeFieldParamss];
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
NSUInteger numRequests = [moduleIDs count];
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramss count];
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
if (!allSame) {
RCTLogMustFix(@"Invalid data message - all must be length: %zd", numRequests);
return;
@ -288,31 +277,102 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool {
[self _handleRequestNumber:i
moduleID:[moduleIDs objectAtIndex:i]
methodID:[methodIDs objectAtIndex:i]
params:[paramss objectAtIndex:i]];
moduleID:[moduleIDs[i] integerValue]
methodID:[methodIDs[i] integerValue]
params:paramsArrays[i]];
}
}
// Update modules
for (id target in _moduleInstances.objectEnumerator) {
if ([target respondsToSelector:@selector(batchDidComplete)]) {
dispatch_async(_shadowQueue, ^{
// TODO: only used by RCTUIManager - can we eliminate this special case?
dispatch_async(_shadowQueue, ^{
for (id target in _moduleInstances.objectEnumerator) {
if ([target respondsToSelector:@selector(batchDidComplete)]) {
[target batchDidComplete];
});
}
}
}
});
}
- (void)_handleRequestNumber:(NSUInteger)i moduleID:(id)moduleID methodID:(id)methodID params:(id)params
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSInteger)moduleID
methodID:(NSInteger)methodID
params:(NSArray *)params
{
if (![moduleID isKindOfClass:[NSNumber class]] || ![methodID isKindOfClass:[NSNumber class]] || ![params isKindOfClass:[NSArray class]]) {
if (![params isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i);
return;
return NO;
}
[self _dispatchUsingAsyncHookMapWithModuleID:[moduleID integerValue]
methodID:[methodID integerValue]
params:params];
if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
return NO;
}
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(moduleID);
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
if (methodID < 0 || methodID >= methods.count) {
return NO;
}
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogMustFix(@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName);
return NO;
}
__weak RCTBridge *weakSelf = self;
dispatch_async(_shadowQueue, ^{
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}
NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
// 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);
[invocation setArgument:&target atIndex:0];
SEL selector = method.selector;
[invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes.
NSMutableArray *blocks = [NSMutableArray array];
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) {
param = nil;
} else if ([method.blockArgumentIndexes containsIndex:idx]) {
id block = [strongSelf createResponseSenderBlock:[param integerValue]];
[blocks addObject:block];
param = block;
}
[invocation setArgument:&param atIndex:idx + 2];
}];
@try {
[invocation invoke];
}
@catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
}
@finally {
// Force `blocks` to remain alive until here.
blocks = nil;
}
});
return YES;
}
/**
@ -352,84 +412,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return invocation;
}
- (BOOL)_dispatchUsingAsyncHookMapWithModuleID:(NSInteger)moduleID
methodID:(NSInteger)methodID
params:(NSArray *)params
{
if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
return NO;
}
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(moduleID);
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
if (methodID < 0 || methodID >= methods.count) {
return NO;
}
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogMustFix(
@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName
);
return NO;
}
__weak RCTBridge *weakSelf = self;
dispatch_async(_shadowQueue, ^{
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}
NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
// 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);
[invocation setArgument:&target atIndex:0];
SEL selector = method.selector;
[invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes.
NSMutableArray *blocks = [NSMutableArray array];
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) {
param = nil;
} else if ([method.blockArgumentIndexes containsIndex:idx]) {
id block = [strongSelf createResponseSenderBlock:[param integerValue]];
[blocks addObject:block];
param = block;
}
[invocation setArgument:&param atIndex:idx + 2];
}];
@try {
[invocation invoke];
}
@catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
}
@finally {
// Force `blocks` to remain alive until here.
blocks = nil;
}
});
return YES;
}
- (void)doneRegisteringModules
{
RCTAssertMainThread();
@ -456,8 +438,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
moduleConfig[@"methods"] = methods;
id target = [_moduleInstances objectForKey:moduleName];
if ([target respondsToSelector:@selector(constantsToExport)] && ![target conformsToProtocol:@protocol(RCTNativeViewModule)]) {
// TODO: find a more elegant way to handle RCTNativeViewModule constants as a special case
if ([target respondsToSelector:@selector(constantsToExport)]) {
moduleConfig[@"constants"] = [target constantsToExport];
}
moduleConfigs[moduleName] = moduleConfig;
@ -484,6 +465,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
- (void)registerRootView:(RCTRootView *)rootView
{
// TODO: only used by RCTUIManager - can we eliminate this special case?
for (id target in _moduleInstances.objectEnumerator) {
if ([target respondsToSelector:@selector(registerRootView:)]) {
[target registerRootView:rootView];
}
}
}
+ (BOOL)hasValidJSExecutor
{
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);

View File

@ -4,6 +4,7 @@
#import "RCTLog.h"
@class RCTBridge;
@class RCTSparseArray;
@class RCTUIManager;
@ -42,6 +43,12 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
@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
@ -74,7 +81,7 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
/**
* Provides minimal interface needed to register a UIViewManager module
*/
@protocol RCTNativeViewModule <RCTNativeModule>
@protocol RCTNativeViewModule <NSObject>
/**
* This method instantiates a native view to be managed by the module.
@ -83,6 +90,12 @@ _RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __
@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.
@ -167,6 +180,12 @@ RCT_REMAP_VIEW_PROPERTY(name, name)
*/
- (NSDictionary *)customDirectEventTypes;
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.moduleName.X.
*/
- (NSDictionary *)constantsToExport;
/**
* To deprecate, hopefully
*/

View File

@ -14,9 +14,7 @@
* JavaScript so that applications can clean up resources. (launch blocker).
* TODO: Incremental module loading. (low pri).
*/
@interface RCTJavaScriptAppEngine : NSObject <RCTInvalidating>
@property (nonatomic, readonly, strong) RCTBridge *bridge;
@interface RCTJavaScriptAppEngine : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete;

View File

@ -22,11 +22,8 @@
*/
@implementation RCTJavaScriptAppEngine
{
BOOL _isPaused; // Pauses drawing/updating of the JSView
BOOL _pauseOnEnterBackground;
CADisplayLink *_displayLink;
NSTimer *_runTimer;
NSDictionary *_loadedResource;
__weak RCTBridge *_bridge;
}
- (instancetype)init
@ -54,118 +51,13 @@
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
if ((self = [super init])) {
_bridge = bridge;
_isPaused = NO;
self.pauseOnEnterBackground = YES;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(run:)];
if (_displayLink) {
[_displayLink setFrameInterval:1];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_runTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:@selector(run:) userInfo:nil repeats:YES];
}
}
return self;
}
/**
* TODO: Wait until operations on `javaScriptQueue` are complete.
*/
- (void)dealloc
{
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
[_bridge invalidate];
_bridge = nil;
[_displayLink invalidate];
_displayLink = nil;
// Remove from notification center
self.pauseOnEnterBackground = NO;
}
#pragma mark - Run loop
- (void)run:(CADisplayLink *)sender
{
if (!_isPaused) {
RCTAssertMainThread();
[_bridge enqueueUpdateTimers];
}
}
- (void)pauseRunLoop
{
if (!_isPaused) {
[_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_isPaused = YES;
}
}
- (void)resumeRunLoop
{
if (_isPaused) {
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_isPaused = NO;
}
}
/**
* See warnings from lint: UIApplicationDidBecomeActive fires in a critical
* foreground path, and prevents the app from prioritizing the foreground
* processing. Consider using
* FBApplicationDidFinishEnteringForegroundAndIsNowIdleNotification.
*/
- (void)setPauseOnEnterBackground:(BOOL)pauses
{
NSArray *pauseN = @[
UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification
];
NSArray *resumeN =
@[UIApplicationWillEnterForegroundNotification, UIApplicationDidBecomeActiveNotification];
if (pauses) {
[self observeKeyPaths:pauseN selector:@selector(pauseRunLoop)];
[self observeKeyPaths:resumeN selector:@selector(resumeRunLoop)];
}
else {
[self removeObserverForKeyPaths:pauseN];
[self removeObserverForKeyPaths:resumeN];
}
_pauseOnEnterBackground = pauses;
}
- (void)removeObserverForKeyPaths:(NSArray*)keyPaths
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
for (NSString *name in keyPaths) {
[nc removeObserver:self name:name object:nil];
}
}
- (void)observeKeyPaths:(NSArray*)keyPaths selector:(SEL)selector
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
for (NSString *name in keyPaths) {
[nc addObserver:self selector:selector name:name object:nil];
}
}
#pragma mark - Module and script loading
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL

View File

@ -18,7 +18,6 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
@implementation RCTRootView
{
dispatch_queue_t _shadowQueue;
RCTBridge *_bridge;
RCTJavaScriptAppEngine *_appEngine;
RCTTouchHandler *_touchHandler;
@ -62,9 +61,6 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
- (void)setUp
{
// TODO: does it make sense to do this here? What if there's more than one host view?
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Every root view that is created must have a unique react tag.
// Numbering of these tags goes from 1, 11, 21, 31, etc
static NSInteger rootViewTag = 1;
@ -95,16 +91,16 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
}
} else {
[_bridge.uiManager registerRootView:self];
[_bridge registerRootView:self];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": self.reactTag ?: @0,
@"initialProps": self.initialProperties ?: @{},
};
[_appEngine.bridge enqueueJSCall:RCTModuleIDBundler
methodID:RCTBundlerRunApplication
args:@[moduleName, appParameters]];
[_bridge enqueueJSCall:RCTModuleIDBundler
methodID:RCTBundlerRunApplication
args:@[moduleName, appParameters]];
}
}
@ -125,12 +121,10 @@ NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"
};
[_executor invalidate];
[_appEngine invalidate];
[_bridge invalidate];
_executor = [[RCTContextExecutor alloc] init];
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor
shadowQueue:_shadowQueue
javaScriptModulesConfig:[RCTModuleIDs config]];
_appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge];

View File

@ -3,12 +3,12 @@
#import <Foundation/Foundation.h>
#import "RCTExport.h"
#import "RCTInvalidating.h"
@class RCTBridge;
@interface RCTTiming : NSObject <RCTNativeModule>
@interface RCTTiming : NSObject <RCTNativeModule, RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge;
- (void)enqueueUpdateTimers;
@end

View File

@ -11,7 +11,6 @@
@interface RCTTimer : NSObject
@property (nonatomic, strong, readonly) NSDate *target;
@property (nonatomic, assign, readonly, getter=isActive) BOOL active;
@property (nonatomic, assign, readonly) BOOL repeats;
@property (nonatomic, strong, readonly) NSNumber *callbackID;
@property (nonatomic, assign, readonly) NSTimeInterval interval;
@ -26,7 +25,6 @@
repeats:(BOOL)repeats
{
if ((self = [super init])) {
_active = YES;
_interval = interval;
_repeats = repeats;
_callbackID = callbackID;
@ -40,13 +38,9 @@
*/
- (BOOL)updateFoundNeedsJSUpdate
{
if (_active && _target.timeIntervalSinceNow <= 0) {
if (_target && _target.timeIntervalSinceNow <= 0) {
// The JS Timers will do fine grained calculating of expired timeouts.
if (_repeats) {
_target = [NSDate dateWithTimeIntervalSinceNow:_interval];
} else {
_active = NO;
}
_target = _repeats ? [NSDate dateWithTimeIntervalSinceNow:_interval] : nil;
return YES;
}
return NO;
@ -58,6 +52,7 @@
{
RCTSparseArray *_timers;
RCTBridge *_bridge;
id _updateTimer;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@ -65,19 +60,74 @@
if ((self = [super init])) {
_bridge = bridge;
_timers = [[RCTSparseArray alloc] init];
[self startTimers];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(stopTimers)
name:name
object:nil];
}
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationWillEnterForegroundNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startTimers)
name:name
object:nil];
}
}
return self;
}
/**
* TODO (#5906496): Wait until operations on `javaScriptQueue` are complete to complete the
* `dealloc`.
*/
/* - (void)dealloc
{
} */
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)enqueueUpdateTimers
- (BOOL)isValid
{
return _bridge != nil;
}
- (void)invalidate
{
[self stopTimers];
_bridge = nil;
}
- (void)stopTimers
{
[_updateTimer invalidate];
_updateTimer = nil;
}
- (void)startTimers
{
RCTAssertMainThread();
if (![self isValid] || _updateTimer != nil) {
return;
}
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
if (_updateTimer) {
[_updateTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
target:self
selector:@selector(update)
userInfo:nil
repeats:YES];
}
}
- (void)update
{
RCTAssertMainThread();
@ -86,7 +136,7 @@
if ([timer updateFoundNeedsJSUpdate]) {
[timersToCall addObject:timer.callbackID];
}
if (!timer.active) {
if (!timer.target) {
_timers[timer.callbackID] = nil;
}
}
@ -97,14 +147,6 @@
}
}
- (void)scheduleCallbackID:(NSNumber *)callbackID interval:(NSTimeInterval)interval targetTime:(NSTimeInterval)targetTime repeats:(BOOL)repeats
{
dispatch_async(dispatch_get_main_queue(), ^{
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID interval:interval targetTime:targetTime repeats:repeats];
_timers[callbackID] = timer;
});
}
/**
* There's a small difference between the time when we call
* setTimeout/setInterval/requestAnimation frame and the time it actually makes
@ -132,10 +174,13 @@
interval = 0;
}
[self scheduleCallbackID:callbackID
interval:interval
targetTime:targetTime
repeats:repeats.boolValue];
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
interval:interval
targetTime:targetTime
repeats:repeats];
dispatch_async(dispatch_get_main_queue(), ^{
_timers[callbackID] = timer;
});
}
- (void)deleteTimer:(NSNumber *)timerID

View File

@ -5,7 +5,7 @@
#import "RCTExport.h"
#import "RCTInvalidating.h"
@class RCTAnimationRegistry;
@class RCTBridge;
@class RCTRootView;
@class RCTShadowView;
@ -14,13 +14,10 @@
@interface RCTUIManager : NSObject <RCTInvalidating, RCTNativeModule>
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue
viewManagers:(NSDictionary *)viewManagers;
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@property (nonatomic, strong) RCTEventDispatcher *eventDispatcher;
@property (nonatomic, strong) RCTSparseArray *shadowViewRegistry;
@property (nonatomic, strong) RCTSparseArray *viewRegistry;
@property (nonatomic, strong) RCTAnimationRegistry *animationRegistry;
@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
/**

View File

@ -34,6 +34,64 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
}
}
static NSString *RCTModuleName(Class moduleClass)
{
if ([moduleClass respondsToSelector:@selector(moduleName)]) {
return [moduleClass moduleName];
} else {
// Default implementation, works in most cases
NSString *className = NSStringFromClass(moduleClass);
if ([className hasPrefix:@"RCTUI"]) {
className = [className substringFromIndex:@"RCT".length];
}
if ([className hasSuffix:@"Manager"]) {
className = [className substringToIndex:className.length - @"Manager".length];
}
return className;
}
}
static NSDictionary *RCTViewModuleClasses(void)
{
static NSMutableDictionary *modules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
modules = [NSMutableDictionary dictionary];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}
if (![cls conformsToProtocol:@protocol(RCTNativeViewModule)]) {
// Not an RCTNativeModule
continue;
}
// Get module name
NSString *moduleName = RCTModuleName(cls);
// 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);
modules[moduleName] = cls;
}
free(classes);
});
return modules;
}
@implementation RCTUIManager
{
// Root views are only mutated on the shadow queue
@ -42,13 +100,14 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
NSMutableArray *_pendingUIBlocks;
pthread_mutex_t _pendingUIBlocksMutex;
dispatch_queue_t _shadowQueue;
NSDictionary *_nextLayoutAnimationConfig; // RCT thread only
RCTResponseSenderBlock _nextLayoutAnimationCallback; // RCT thread only
RCTResponseSenderBlock _layoutAnimationCallbackMT; // Main thread only
NSMutableDictionary *_defaultShadowViews;
NSMutableDictionary *_defaultViews;
__weak RCTBridge *_bridge;
}
- (id <RCTNativeViewModule>)_managerInstanceForViewWithModuleName:(NSString *)moduleName
@ -62,17 +121,23 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
return managerInstance;
}
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue
viewManagers:(NSDictionary *)viewManagers
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
pthread_mutex_init(&_pendingUIBlocksMutex, NULL);
// Instantiate view managers
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
viewManagers[moduleName] = [[moduleClass alloc] init];
}];
_viewManagers = viewManagers;
_viewRegistry = [[RCTSparseArray alloc] init];
_shadowViewRegistry = [[RCTSparseArray alloc] init];
_shadowQueue = shadowQueue;
pthread_mutex_init(&_pendingUIBlocksMutex, NULL);
// Internal resources
_pendingUIBlocks = [[NSMutableArray alloc] init];
_rootViewTags = [[NSMutableSet alloc] init];
@ -101,6 +166,8 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
- (void)invalidate
{
RCTAssertMainThread();
_viewRegistry = nil;
_shadowViewRegistry = nil;
@ -111,6 +178,8 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
- (void)registerRootView:(RCTRootView *)rootView;
{
RCTAssertMainThread();
NSNumber *reactTag = rootView.reactTag;
UIView *existingView = _viewRegistry[reactTag];
RCTCAssert(existingView == nil || existingView == rootView,
@ -121,7 +190,7 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
CGRect frame = rootView.frame;
// Register shadow view
dispatch_async(_shadowQueue, ^{
dispatch_async(_bridge.shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
@ -468,7 +537,7 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
- (UIView *)viewForViewManager:(id <RCTNativeViewModule>)manager
{
return [manager viewWithEventDispatcher:_eventDispatcher];
return [manager viewWithEventDispatcher:_bridge.eventDispatcher];
}
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, id <RCTNativeViewModule>manager)
@ -610,25 +679,25 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
// First copy the previous blocks into a temporary variable, then reset the
// pending blocks to a new array. This guards against mutation while
// processing the pending blocks in another thread.
for (id <RCTNativeViewModule>viewManager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [viewManager respondsToSelector:@selector(uiBlockToAmendWithShadowViewRegistry:)] ? [viewManager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry] : nil;
if (uiBlock != nil) {
[self addUIBlock:uiBlock];
}
}
for (NSNumber *reactTag in _rootViewTags) {
RCTShadowView *rootView = _shadowViewRegistry[reactTag];
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView];
}
pthread_mutex_lock(&_pendingUIBlocksMutex);
NSArray *previousPendingUIBlocks = _pendingUIBlocks;
_pendingUIBlocks = [[NSMutableArray alloc] init];
pthread_mutex_unlock(&_pendingUIBlocksMutex);
dispatch_async(dispatch_get_main_queue(), ^{
for (dispatch_block_t block in previousPendingUIBlocks) {
block();