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

@ -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,47 +91,31 @@ 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;
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// 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
// Register modules
_moduleInstances = [[NSMutableDictionary alloc] init];
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) {
if ([moduleClass instancesRespondToSelector:@selector(initWithBridge:)]) {
_moduleInstances[moduleName] = [[moduleClass alloc] initWithBridge:self];
} else {
_moduleInstances[moduleName] = [[moduleClass alloc] init];
}
}
}];
[self doneRegisteringModules];
@ -148,8 +144,8 @@ 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) {
@ -158,8 +154,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
}
[_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,74 +277,32 @@ 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
// 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)]) {
dispatch_async(_shadowQueue, ^{
[target batchDidComplete];
}
}
});
}
}
}
- (void)_handleRequestNumber:(NSUInteger)i moduleID:(id)moduleID methodID:(id)methodID params:(id)params
{
if (![moduleID isKindOfClass:[NSNumber class]] || ![methodID isKindOfClass:[NSNumber class]] || ![params isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i);
return;
}
[self _dispatchUsingAsyncHookMapWithModuleID:[moduleID integerValue]
methodID:[methodID integerValue]
params:params];
}
/**
* Returns a callback that reports values back to the JS thread.
* TODO (#5906496): These responses should go into their own queue `MessageQueue.m` that
* mirrors the JS queue and protocol. For now, we speak the "language" of the JS
* queue by packing it into an array that matches the wire protocol.
*/
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)cbID
{
if (!cbID) {
return nil;
}
return ^(NSArray *args) {
[self _sendResponseToJavaScriptCallbackID:cbID args:args];
};
}
+ (NSInvocation *)invocationForAdditionalArguments:(NSUInteger)argCount
{
static NSMutableDictionary *invocations;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
invocations = [NSMutableDictionary dictionary];
});
id key = @(argCount);
NSInvocation *invocation = invocations[key];
if (invocation == nil) {
NSString *objCTypes = [@"v@:" stringByPaddingToLength:3 + argCount withString:@"@" startingAtIndex:0];
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:objCTypes.UTF8String];
invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocations[key] = invocation;
}
return invocation;
}
- (BOOL)_dispatchUsingAsyncHookMapWithModuleID:(NSInteger)moduleID
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSInteger)moduleID
methodID:(NSInteger)methodID
params:(NSArray *)params
{
if (![params isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
}
if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
return NO;
}
@ -369,13 +316,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogMustFix(
@"Expected %tu arguments but got %tu invoking %@.%@",
RCTLogMustFix(@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName
);
method.JSMethodName);
return NO;
}
@ -430,6 +375,43 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return YES;
}
/**
* Returns a callback that reports values back to the JS thread.
* TODO (#5906496): These responses should go into their own queue `MessageQueue.m` that
* mirrors the JS queue and protocol. For now, we speak the "language" of the JS
* queue by packing it into an array that matches the wire protocol.
*/
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)cbID
{
if (!cbID) {
return nil;
}
return ^(NSArray *args) {
[self _sendResponseToJavaScriptCallbackID:cbID args:args];
};
}
+ (NSInvocation *)invocationForAdditionalArguments:(NSUInteger)argCount
{
static NSMutableDictionary *invocations;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
invocations = [NSMutableDictionary dictionary];
});
id key = @(argCount);
NSInvocation *invocation = invocations[key];
if (invocation == nil) {
NSString *objCTypes = [@"v@:" stringByPaddingToLength:3 + argCount withString:@"@" startingAtIndex:0];
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:objCTypes.UTF8String];
invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocations[key] = invocation;
}
return invocation;
}
- (void)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,14 +91,14 @@ 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
[_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
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
interval:interval
targetTime:targetTime
repeats:repeats.boolValue];
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,16 +121,22 @@ 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];
@ -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)