[ReactNative] Create private underlying bridge to prevent retain cycles

This commit is contained in:
Tadeu Zagallo 2015-05-04 10:35:49 -07:00
parent b532ec000f
commit 132a9170f1
12 changed files with 522 additions and 349 deletions

View File

@ -17,6 +17,12 @@
#define TIMEOUT_SECONDS 240
@interface RCTBridge (RCTTestRunner)
@property (nonatomic, weak) RCTBridge *batchedBridge;
@end
@implementation RCTTestRunner
{
FBSnapshotTestController *_testController;
@ -66,7 +72,7 @@
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
RCTTestModule *testModule = rootView.bridge.batchedBridge.modules[testModuleName];
testModule.controller = _testController;
testModule.testSelector = test;
testModule.view = rootView;
@ -83,8 +89,6 @@
error = [[RCTRedBox sharedInstance] currentErrorMessage];
}
[rootView removeFromSuperview];
[rootView.bridge invalidate];
[rootView invalidate];
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
vc.view = nil;
[[RCTRedBox sharedInstance] dismiss];

View File

@ -322,23 +322,24 @@ var MessageQueueMixin = {
processBatch: function(batch) {
var self = this;
ReactUpdates.batchedUpdates(function() {
batch.forEach(function(call) {
invariant(
call.module === 'BatchedBridge',
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self.callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self.invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
return guardReturn(function () {
ReactUpdates.batchedUpdates(function() {
batch.forEach(function(call) {
invariant(
call.module === 'BatchedBridge',
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self._callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self._invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
});
});
});
return this.flushedQueue();
}, null, this._flushedQueueUnguarded, this);
},
setLoggingEnabled: function(enabled) {

View File

@ -80,15 +80,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
__attribute__((used, section("__DATA,RCTImport"))) \
static const char *__rct_import_##module##_##method##__ = #module"."#method;
/**
* This method is used to execute a new application script. It is called
* internally whenever a JS application bundle is loaded/reloaded, but should
* probably not be used at any other time.
*/
- (void)enqueueApplicationScript:(NSString *)script
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
/**
* URL of the script that was loaded into the bridge.
*/
@ -122,14 +113,4 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
*/
- (void)reload;
/**
* Add a new observer that will be called on every screen refresh.
*/
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
/**
* Stop receiving screen refresh updates for the given observer.
*/
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
@end

View File

@ -25,6 +25,7 @@
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTSourceCode.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
@ -45,12 +46,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};
/**
* Temporarily allow to turn on and off the call batching in case someone wants
* to profile both
*/
#define BATCHED_BRIDGE 0
#ifdef __LP64__
typedef uint64_t RCTHeaderValue;
typedef struct section_64 RCTHeaderSection;
@ -61,6 +56,11 @@ typedef struct section RCTHeaderSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader
#endif
#define RCTAssertJSThread() \
RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTContextExecutor"] || \
[[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
@"This method must be called on JS thread")
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
@ -122,6 +122,7 @@ static NSArray *RCTJSMethods(void)
* RTCBridgeModule protocol to ensure they've been exported. This scanning
* functionality is disabled in release mode to improve startup performance.
*/
static NSDictionary *RCTModuleIDsByName;
static NSArray *RCTModuleNamesByID;
static NSArray *RCTModuleClassesByID;
static NSArray *RCTBridgeModuleClassesByModuleID(void)
@ -129,8 +130,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleNamesByID = [NSMutableArray array];
RCTModuleClassesByID = [NSMutableArray array];
RCTModuleIDsByName = [[NSMutableDictionary alloc] init];
RCTModuleNamesByID = [[NSMutableArray alloc] init];
RCTModuleClassesByID = [[NSMutableArray alloc] init];
Dl_info info;
dladdr(&RCTBridgeModuleClassesByModuleID, &info);
@ -162,7 +164,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
NSStringFromClass(cls));
// Register module
[(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)];
NSString *moduleName = RCTBridgeModuleNameForClass(cls);
((NSMutableDictionary *)RCTModuleIDsByName)[moduleName] = @(RCTModuleNamesByID.count);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
[(NSMutableArray *)RCTModuleClassesByID addObject:cls];
}
}
@ -199,20 +203,31 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
return RCTModuleClassesByID;
}
@class RCTBatchedBridge;
@interface RCTBridge ()
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
@property (nonatomic, strong) RCTBridgeModuleProviderBlock moduleProvider;
@property (nonatomic, strong, readwrite) RCTEventDispatcher *eventDispatcher;
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context;
#if BATCHED_BRIDGE
@end
@interface RCTBatchedBridge : RCTBridge <RCTInvalidating>
@property (nonatomic, weak) RCTBridge *parentBridge;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context;
#endif
@end
@ -238,8 +253,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
dispatch_block_t _methodQueue;
}
static Class _globalExecutorClass;
static NSString *RCTStringUpToFirstArgument(NSString *methodName)
{
NSRange colonRange = [methodName rangeOfString:@":"];
@ -702,6 +715,7 @@ static NSDictionary *RCTLocalModulesConfig()
@"methods": [[NSMutableDictionary alloc] init]
};
localModules[moduleName] = module;
[RCTLocalModuleNames addObject:moduleName];
}
// Add method if it doesn't already exist
@ -712,72 +726,18 @@ static NSDictionary *RCTLocalModulesConfig()
@"methodID": @(methods.count),
@"type": @"local"
};
[RCTLocalMethodNames addObject:methodName];
}
// Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
[RCTLocalModuleNames addObject:moduleName];
[RCTLocalMethodNames addObject:methodName];
}
});
return localModules;
}
@interface RCTDisplayLink : NSObject <RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector NS_DESIGNATED_INITIALIZER;
@end
@interface RCTBridge (RCTDisplayLink)
- (void)_update:(CADisplayLink *)displayLink;
@end
@implementation RCTDisplayLink
{
__weak RCTBridge *_bridge;
CADisplayLink *_displayLink;
SEL _selector;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector
{
if ((self = [super init])) {
_bridge = bridge;
_selector = selector;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
return self;
}
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
if (self.isValid) {
[_displayLink invalidate];
_displayLink = nil;
}
}
- (void)_update:(CADisplayLink *)displayLink
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_bridge performSelector:_selector withObject:displayLink];
#pragma clang diagnostic pop
}
@end
@interface RCTFrameUpdate (Private)
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink;
@ -798,22 +758,6 @@ static NSDictionary *RCTLocalModulesConfig()
@end
@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSURL *_bundleURL;
RCTBridgeModuleProviderBlock _moduleProvider;
RCTDisplayLink *_displayLink;
RCTDisplayLink *_vsyncDisplayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
RCTSparseArray *_scheduledCallbacks;
BOOL _loading;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
@ -821,36 +765,226 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
RCTAssertMainThread();
if ((self = [super init])) {
/**
* Pre register modules
*/
RCTLocalModulesConfig();
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
[self setUp];
}
return self;
}
- (void)dealloc
{
/**
* This runs only on the main thread, but crashes the subclass
* RCTAssertMainThread();
*/
[self invalidate];
}
- (void)bindKeys
{
RCTAssertMainThread();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
#if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
#endif
}
- (void)reload
{
/**
* AnyThread
*/
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
[self setUp];
});
}
- (void)setUp
{
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
RCTAssertMainThread();
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)];
}];
_vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)];
_batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
}
- (BOOL)isValid
{
return _batchedBridge.isValid;
}
- (void)invalidate
{
RCTAssertMainThread();
[_batchedBridge invalidate];
_batchedBridge = nil;
}
+ (void)logMessage:(NSString *)message level:(NSString *)level
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!_latestJSExecutor.isValid) {
return;
}
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:@[level, message]
context:RCTGetExecutorID(_latestJSExecutor)
callback:^(id json, NSError *error) {}];
});
}
- (NSDictionary *)modules
{
return _batchedBridge.modules;
}
#define RCT_BRIDGE_WARN(...) \
- (void)__VA_ARGS__ \
{ \
RCTLogMustFix(@"Called method \"%@\" on top level bridge. This method should \
only be called from bridge instance in a bridge module", @(__func__)); \
}
RCT_BRIDGE_WARN(enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args)
RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context)
@end
@implementation RCTBatchedBridge
{
BOOL _loading;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
CADisplayLink *_mainDisplayLink;
CADisplayLink *_jsDisplayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
RCTSparseArray *_scheduledCallbacks;
}
@synthesize valid = _valid;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
RCTAssertMainThread();
_parentBridge = bridge;
/**
* Set Initial State
*/
_valid = YES;
_loading = YES;
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
_queuesByID = [[RCTSparseArray alloc] init];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
/**
* Initialize executor to allow enqueueing calls
*/
Class executorClass = self.executorClass ?: [RCTContextExecutor class];
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_latestJSExecutor = _javaScriptExecutor;
/**
* Setup event dispatcher before initializing modules to allow init calls
*/
self.eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
/**
* Initialize and register bridge modules *before* adding the display link
* so we don't have threading issues
*/
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
[self registerModules];
/**
* Start the application script
*/
[self initJS];
}
return self;
}
- (NSDictionary *)launchOptions
{
return _parentBridge.launchOptions;
}
/**
* Override to ensure that we won't create another nested bridge
*/
- (void)setUp {}
- (void)reload
{
[_parentBridge reload];
}
- (Class)executorClass
{
return _parentBridge.executorClass;
}
- (void)setExecutorClass:(Class)executorClass
{
RCTAssertMainThread();
_parentBridge.executorClass = executorClass;
}
- (BOOL)isLoading
{
return _loading;
}
- (BOOL)isValid
{
return _valid;
}
- (void)registerModules
{
RCTAssertMainThread();
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
for (id<RCTBridgeModule> module in _parentBridge.moduleProvider ? _parentBridge.moduleProvider() : nil) {
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
}
@ -897,7 +1031,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
// Get method queues
_queuesByID = [[RCTSparseArray alloc] init];
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t queue = [module methodQueue];
@ -907,7 +1040,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_queuesByID[moduleID] = [NSNull null];
}
}
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:module];
}
}];
}
- (void)initJS
{
RCTAssertMainThread();
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@ -920,7 +1062,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
dispatch_semaphore_signal(semaphore);
}];
_loading = YES;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
NSURL *bundleURL = _parentBridge.bundleURL;
if (_javaScriptExecutor == nil) {
/**
@ -929,11 +1073,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
_loading = NO;
} else if (_bundleURL) { // Allow testing without a script
} else if (bundleURL) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) {
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
_loading = NO;
if (!self.isValid) {
return;
}
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
@ -946,37 +1096,25 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
} else {
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
if (!loadError) {
/**
* Register the display link to start sending js calls after everything
* is setup
*/
[_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
}
}];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}];
}
}
- (void)bindKeys
{
#if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
#endif
}
- (NSDictionary *)modules
{
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
@ -985,53 +1123,48 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return _modulesByName;
}
- (void)dealloc
{
[self invalidate];
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return _javaScriptExecutor != nil;
}
- (void)invalidate
{
if (!self.isValid && _modulesByID == nil) {
if (!self.isValid) {
return;
}
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
return;
}
RCTAssertMainThread();
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Release executor
_valid = NO;
if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil;
}
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
[_displayLink invalidate];
[_vsyncDisplayLink invalidate];
_frameUpdateObservers = nil;
/**
* Main Thread deallocations
*/
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_mainDisplayLink invalidate];
// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
[(id<RCTInvalidating>)target invalidate];
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
/**
* JS Thread deallocations
*/
[_javaScriptExecutor invalidate];
[_jsDisplayLink invalidate];
// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
[(id<RCTInvalidating>)target invalidate];
}
}
}
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_queuesByID = nil;
_modulesByName = nil;
// Release modules (breaks retain cycle if module has strong bridge reference)
_javaScriptExecutor = nil;
_frameUpdateObservers = nil;
_modulesByID = nil;
_queuesByID = nil;
_modulesByName = nil;
}];
}
/**
@ -1066,6 +1199,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
RCTAssertJSThread();
NSString *moduleDotMethod = @"RCTJSTimers.callTimers";
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
@ -1074,35 +1209,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (!_loading) {
#if BATCHED_BRIDGE
dispatch_block_t block = ^{
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
dispatch_block_t block = ^{
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
};
#else
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
#endif
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
}
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTAssertJSThread();
RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError);
if (scriptLoadError) {
onComplete(scriptLoadError);
@ -1132,7 +1261,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
{
id queue = _queuesByID[moduleID];
RCTAssertJSThread();
id queue = nil;
if (moduleID) {
queue = _queuesByID[moduleID];
}
if (queue == [NSNull null]) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else {
@ -1146,13 +1281,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#if BATCHED_BRIDGE
/**
* AnyThread
*/
__weak RCTBridge *weakSelf = self;
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileBeginEvent();
RCTBridge *strongSelf = weakSelf;
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) {
return;
}
@ -1190,7 +1327,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
if (
[args[2][0] isEqual:callArgs[2][0]] &&
([moduleName isEqualToString:@"RCTEventEmitter"] ? [args[2][1] isEqual:callArgs[2][1]] : YES)
(![moduleName isEqualToString:@"RCTEventEmitter"] || [args[2][1] isEqual:callArgs[2][1]])
) {
[strongSelf->_scheduledCalls removeObject:call];
}
@ -1218,10 +1355,14 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#endif
RCTAssertJSThread();
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (!self.isValid) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self _handleBuffer:json context:context];
};
@ -1237,6 +1378,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
{
RCTAssertJSThread();
if (buffer == nil || buffer == (id)kCFNull) {
return;
}
@ -1307,6 +1450,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
params:(NSArray *)params
context:(NSNumber *)context
{
RCTAssertJSThread();
if (!self.isValid) {
return NO;
}
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
@ -1330,10 +1478,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return NO;
}
__weak RCTBridge *weakSelf = self;
__weak RCTBatchedBridge *weakSelf = self;
[self dispatchBlock:^{
RCTProfileBeginEvent();
__strong RCTBridge *strongSelf = weakSelf;
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
@ -1367,18 +1515,21 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertJSThread();
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
RCTProfileBeginEvent();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
[observer didUpdateFrame:frameUpdate];
[self dispatchBlock:^{
[observer didUpdateFrame:frameUpdate];
} forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]];
}
}
#if BATCHED_BRIDGE
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
@ -1393,67 +1544,41 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
context:RCTGetExecutorID(_javaScriptExecutor)];
}
#endif
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
}
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
{
[_frameUpdateObservers addObject:observer];
}
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
{
[_frameUpdateObservers removeObject:observer];
}
- (void)reload
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!_loading) {
// If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render.
[self invalidate];
[self setUp];
}
});
}
+ (void)logMessage:(NSString *)message level:(NSString *)level
{
if (![_latestJSExecutor isValid]) {
return;
}
// Note: the js executor could get invalidated while we're trying to call
// this...need to watch out for that.
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:@[level, message]
context:RCTGetExecutorID(_latestJSExecutor)
callback:^(id json, NSError *error) {}];
}
- (void)startProfiling
{
if (![_bundleURL.scheme isEqualToString:@"http"]) {
RCTAssertMainThread();
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
RCTLogError(@"To run the profiler you must be running from the dev server");
return;
}
[_mainDisplayLink invalidate];
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
RCTProfileInit();
}
- (void)stopProfiling
{
RCTAssertMainThread();
[_mainDisplayLink invalidate];
NSString *log = RCTProfileEnd();
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
URLRequest.HTTPMethod = @"POST";

View File

@ -123,10 +123,12 @@ RCT_EXPORT_MODULE()
{
_settings = [NSMutableDictionary dictionaryWithDictionary:[_defaults objectForKey:RCTDevMenuSettingsKey]];
self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
dispatch_async(dispatch_get_main_queue(), ^{
self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
});
}
- (void)jsLoaded
@ -147,10 +149,12 @@ RCT_EXPORT_MODULE()
_liveReloadURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
}
// Hit these setters again after bridge has finished loading
self.profilingEnabled = _profilingEnabled;
self.liveReloadEnabled = _liveReloadEnabled;
self.executorClass = _executorClass;
dispatch_async(dispatch_get_main_queue(), ^{
// Hit these setters again after bridge has finished loading
self.profilingEnabled = _profilingEnabled;
self.liveReloadEnabled = _liveReloadEnabled;
self.executorClass = _executorClass;
});
}
- (void)dealloc

View File

@ -22,6 +22,6 @@
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(RCTJavaScriptCompleteBlock)onComplete;
- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(void (^)(NSError *, NSString *))onComplete;
@end

View File

@ -27,7 +27,7 @@
return self;
}
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *, NSString *))onComplete
{
// Sanitize the script URL
scriptURL = [RCTConvert NSURL:scriptURL.absoluteString];
@ -37,7 +37,7 @@
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
NSLocalizedDescriptionKey: scriptURL ? [NSString stringWithFormat:@"Script at '%@' could not be found.", scriptURL] : @"No script URL provided"
}];
onComplete(error);
onComplete(error, nil);
return;
}
@ -57,7 +57,7 @@
code:error.code
userInfo:userInfo];
}
onComplete(error);
onComplete(error, nil);
return;
}
@ -96,18 +96,10 @@
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
onComplete(error);
onComplete(error, nil);
return;
}
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(scriptError);
});
}];
onComplete(nil, rawText);
}];
[task resume];

View File

@ -11,7 +11,7 @@
#import "RCTBridge.h"
@interface RCTRootView : UIView <RCTInvalidating>
@interface RCTRootView : UIView
/**
* - Designated initializer -

View File

@ -20,25 +20,37 @@
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"
@interface RCTBridge (RCTRootView)
@property (nonatomic, weak, readonly) RCTBridge *batchedBridge;
@end
@interface RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag;
@end
@interface RCTRootContentView : RCTView <RCTInvalidating>
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge;
@end
@implementation RCTRootView
{
RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
NSString *_moduleName;
NSDictionary *_launchOptions;
UIView *_contentView;
RCTRootContentView *_contentView;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
{
RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
@ -52,11 +64,11 @@
_moduleName = moduleName;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading)
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
if (!_bridge.loading) {
[self bundleFinishedLoading];
if (!_bridge.batchedBridge.isLoading) {
[self bundleFinishedLoading:_bridge.batchedBridge];
}
}
return self;
@ -73,25 +85,6 @@
return [self initWithBridge:bridge moduleName:moduleName];
}
- (BOOL)isValid
{
return _contentView.userInteractionEnabled;
}
- (void)invalidate
{
_contentView.userInteractionEnabled = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_contentView) {
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[_contentView.reactTag]];
}
}
- (UIViewController *)backingViewController
{
return _backingViewController ?: [super backingViewController];
@ -105,9 +98,19 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
- (void)bundleFinishedLoading
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
[self bundleFinishedLoading:bridge];
}
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!bridge.isValid) {
return;
}
/**
* Every root view that is created must have a unique React tag.
@ -117,19 +120,16 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
* the React tag is assigned every time we load new content.
*/
[_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds];
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_contentView addGestureRecognizer:_touchHandler];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
[self addSubview:_contentView];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": self.initialProperties ?: @{},
@"initialProps": _initialProperties ?: @{},
};
[_bridge.uiManager registerRootView:_contentView];
[_bridge enqueueJSCall:@"AppRegistry.runApplication"
[bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]];
});
}
@ -139,7 +139,6 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
[super layoutSubviews];
if (_contentView) {
_contentView.frame = self.bounds;
[_bridge.uiManager setFrame:self.bounds forRootView:_contentView];
}
}
@ -148,6 +147,12 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
return _contentView.reactTag;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_contentView removeFromSuperview];
}
@end
@implementation RCTUIManager (RCTRootView)
@ -160,3 +165,60 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
}
@end
@implementation RCTRootContentView
{
__weak RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
}
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
[self setUp];
self.frame = frame;
}
return self;
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (self.reactTag && _bridge.isValid) {
[_bridge.uiManager setFrame:self.bounds forRootView:self];
}
}
- (void)setUp
{
/**
* Every root view that is created must have a unique react tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc
*
* NOTE: Since the bridge persists, the RootViews might be reused, so now
* the react tag is assigned every time we load new content.
*/
self.reactTag = [_bridge.uiManager allocateRootTag];
[self addGestureRecognizer:[[RCTTouchHandler alloc] initWithBridge:_bridge]];
[_bridge.uiManager registerRootView:self];
}
- (BOOL)isValid
{
return self.userInteractionEnabled;
}
- (void)invalidate
{
self.userInteractionEnabled = NO;
}
- (void)dealloc
{
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
}
@end

View File

@ -70,6 +70,7 @@
}
@synthesize bridge = _bridge;
@synthesize paused = _paused;
RCT_EXPORT_MODULE()
@ -78,7 +79,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (instancetype)init
{
if ((self = [super init])) {
_paused = YES;
_timers = [[RCTSparseArray alloc] init];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
@ -126,7 +127,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (void)stopTimers
{
[_bridge removeFrameUpdateObserver:self];
_paused = YES;
}
- (void)startTimers
@ -135,7 +136,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
return;
}
[_bridge addFrameUpdateObserver:self];
_paused = NO;
}
- (void)didUpdateFrame:(RCTFrameUpdate *)update

View File

@ -267,11 +267,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
return self;
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
- (BOOL)isValid
{
return _viewRegistry != nil;
@ -279,20 +274,24 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
- (void)invalidate
{
RCTAssertMainThread();
/**
* Called on the JS Thread since all modules are invalidated on the JS thread
*/
for (NSNumber *rootViewTag in _rootViewTags) {
((UIView *)_viewRegistry[rootViewTag]).userInteractionEnabled = NO;
}
dispatch_async(dispatch_get_main_queue(), ^{
for (NSNumber *rootViewTag in _rootViewTags) {
((UIView *)_viewRegistry[rootViewTag]).userInteractionEnabled = NO;
}
_rootViewTags = nil;
_shadowViewRegistry = nil;
_viewRegistry = nil;
_bridge = nil;
_rootViewTags = nil;
_shadowViewRegistry = nil;
_viewRegistry = nil;
_bridge = nil;
[_pendingUIBlocksLock lock];
_pendingUIBlocks = nil;
[_pendingUIBlocksLock unlock];
[_pendingUIBlocksLock lock];
_pendingUIBlocks = nil;
[_pendingUIBlocksLock unlock];
});
}
- (void)setBridge:(RCTBridge *)bridge

View File

@ -264,9 +264,13 @@ NSInteger kNeverProgressed = -10000;
NSInteger _numberOfViewControllerMovesToIgnore;
}
@synthesize paused = _paused;
- (id)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithFrame:CGRectZero])) {
_paused = YES;
_bridge = bridge;
_mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
@ -341,14 +345,14 @@ NSInteger kNeverProgressed = -10000;
_dummyView.frame = (CGRect){{destination}};
_currentlyTransitioningFrom = indexOfFrom;
_currentlyTransitioningTo = indexOfTo;
[_bridge addFrameUpdateObserver:self];
_paused = NO;
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[weakSelf freeLock];
_currentlyTransitioningFrom = 0;
_currentlyTransitioningTo = 0;
_dummyView.frame = CGRectZero;
[_bridge removeFrameUpdateObserver:self];
_paused = YES;
// Reset the parallel position tracker
}];
}