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

View File

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

View File

@ -80,15 +80,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
__attribute__((used, section("__DATA,RCTImport"))) \ __attribute__((used, section("__DATA,RCTImport"))) \
static const char *__rct_import_##module##_##method##__ = #module"."#method; 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. * 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; - (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 @end

View File

@ -25,6 +25,7 @@
#import "RCTProfile.h" #import "RCTProfile.h"
#import "RCTRedBox.h" #import "RCTRedBox.h"
#import "RCTRootView.h" #import "RCTRootView.h"
#import "RCTSourceCode.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@ -45,12 +46,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis RCTBridgeFieldFlushDateMillis
}; };
/**
* Temporarily allow to turn on and off the call batching in case someone wants
* to profile both
*/
#define BATCHED_BRIDGE 0
#ifdef __LP64__ #ifdef __LP64__
typedef uint64_t RCTHeaderValue; typedef uint64_t RCTHeaderValue;
typedef struct section_64 RCTHeaderSection; typedef struct section_64 RCTHeaderSection;
@ -61,6 +56,11 @@ typedef struct section RCTHeaderSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader #define RCTGetSectByNameFromHeader getsectbynamefromheader
#endif #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 RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification"; NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
@ -122,6 +122,7 @@ static NSArray *RCTJSMethods(void)
* RTCBridgeModule protocol to ensure they've been exported. This scanning * RTCBridgeModule protocol to ensure they've been exported. This scanning
* functionality is disabled in release mode to improve startup performance. * functionality is disabled in release mode to improve startup performance.
*/ */
static NSDictionary *RCTModuleIDsByName;
static NSArray *RCTModuleNamesByID; static NSArray *RCTModuleNamesByID;
static NSArray *RCTModuleClassesByID; static NSArray *RCTModuleClassesByID;
static NSArray *RCTBridgeModuleClassesByModuleID(void) static NSArray *RCTBridgeModuleClassesByModuleID(void)
@ -129,8 +130,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
RCTModuleNamesByID = [NSMutableArray array]; RCTModuleIDsByName = [[NSMutableDictionary alloc] init];
RCTModuleClassesByID = [NSMutableArray array]; RCTModuleNamesByID = [[NSMutableArray alloc] init];
RCTModuleClassesByID = [[NSMutableArray alloc] init];
Dl_info info; Dl_info info;
dladdr(&RCTBridgeModuleClassesByModuleID, &info); dladdr(&RCTBridgeModuleClassesByModuleID, &info);
@ -162,7 +164,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
NSStringFromClass(cls)); NSStringFromClass(cls));
// Register module // Register module
[(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)]; NSString *moduleName = RCTBridgeModuleNameForClass(cls);
((NSMutableDictionary *)RCTModuleIDsByName)[moduleName] = @(RCTModuleNamesByID.count);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
[(NSMutableArray *)RCTModuleClassesByID addObject:cls]; [(NSMutableArray *)RCTModuleClassesByID addObject:cls];
} }
} }
@ -199,20 +203,31 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
return RCTModuleClassesByID; return RCTModuleClassesByID;
} }
@class RCTBatchedBridge;
@interface RCTBridge () @interface RCTBridge ()
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
@property (nonatomic, strong) RCTBridgeModuleProviderBlock moduleProvider;
@property (nonatomic, strong, readwrite) RCTEventDispatcher *eventDispatcher;
- (void)_invokeAndProcessModule:(NSString *)module - (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method method:(NSString *)method
arguments:(NSArray *)args arguments:(NSArray *)args
context:(NSNumber *)context; context:(NSNumber *)context;
#if BATCHED_BRIDGE @end
@interface RCTBatchedBridge : RCTBridge <RCTInvalidating>
@property (nonatomic, weak) RCTBridge *parentBridge;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module - (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method method:(NSString *)method
arguments:(NSArray *)args arguments:(NSArray *)args
context:(NSNumber *)context; context:(NSNumber *)context;
#endif
@end @end
@ -238,8 +253,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
dispatch_block_t _methodQueue; dispatch_block_t _methodQueue;
} }
static Class _globalExecutorClass;
static NSString *RCTStringUpToFirstArgument(NSString *methodName) static NSString *RCTStringUpToFirstArgument(NSString *methodName)
{ {
NSRange colonRange = [methodName rangeOfString:@":"]; NSRange colonRange = [methodName rangeOfString:@":"];
@ -702,6 +715,7 @@ static NSDictionary *RCTLocalModulesConfig()
@"methods": [[NSMutableDictionary alloc] init] @"methods": [[NSMutableDictionary alloc] init]
}; };
localModules[moduleName] = module; localModules[moduleName] = module;
[RCTLocalModuleNames addObject:moduleName];
} }
// Add method if it doesn't already exist // Add method if it doesn't already exist
@ -712,72 +726,18 @@ static NSDictionary *RCTLocalModulesConfig()
@"methodID": @(methods.count), @"methodID": @(methods.count),
@"type": @"local" @"type": @"local"
}; };
[RCTLocalMethodNames addObject:methodName];
} }
// Add module and method lookup // Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"]; RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"]; RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
[RCTLocalModuleNames addObject:moduleName];
[RCTLocalMethodNames addObject:methodName];
} }
}); });
return localModules; 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) @interface RCTFrameUpdate (Private)
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink; - (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink;
@ -798,22 +758,6 @@ static NSDictionary *RCTLocalModulesConfig()
@end @end
@implementation RCTBridge @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; static id<RCTJavaScriptExecutor> _latestJSExecutor;
@ -821,36 +765,226 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
moduleProvider:(RCTBridgeModuleProviderBlock)block moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions launchOptions:(NSDictionary *)launchOptions
{ {
RCTAssertMainThread();
if ((self = [super init])) { if ((self = [super init])) {
/**
* Pre register modules
*/
RCTLocalModulesConfig();
_bundleURL = bundleURL; _bundleURL = bundleURL;
_moduleProvider = block; _moduleProvider = block;
_launchOptions = [launchOptions copy]; _launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys]; [self bindKeys];
[self setUp];
} }
return self; 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 - (void)setUp
{ {
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class]; RCTAssertMainThread();
_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];
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ _batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)]; }
}];
_vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)]; - (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 // Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; 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; preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
} }
@ -897,7 +1031,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
} }
// Get method queues // Get method queues
_queuesByID = [[RCTSparseArray alloc] init];
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) { [_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
if ([module respondsToSelector:@selector(methodQueue)]) { if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t queue = [module methodQueue]; dispatch_queue_t queue = [module methodQueue];
@ -907,7 +1040,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_queuesByID[moduleID] = [NSNull null]; _queuesByID[moduleID] = [NSNull null];
} }
} }
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:module];
}
}]; }];
}
- (void)initJS
{
RCTAssertMainThread();
// Inject module data into JS context // Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{ NSString *configJSON = RCTJSONStringify(@{
@ -920,7 +1062,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
dispatch_semaphore_signal(semaphore); dispatch_semaphore_signal(semaphore);
}]; }];
_loading = YES; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
NSURL *bundleURL = _parentBridge.bundleURL;
if (_javaScriptExecutor == nil) { if (_javaScriptExecutor == nil) {
/** /**
@ -929,11 +1073,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/ */
_loading = NO; _loading = NO;
} else if (_bundleURL) { // Allow testing without a script } else if (bundleURL) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) { [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
_loading = NO; _loading = NO;
if (!self.isValid) {
return;
}
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
if (error != nil) { if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"]; NSArray *stack = [[error userInfo] objectForKey:@"stack"];
@ -946,37 +1096,25 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
} }
} else { } else {
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification if (!loadError) {
object:self]; /**
* 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 - (NSDictionary *)modules
{ {
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. " RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
@ -985,53 +1123,48 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return _modulesByName; return _modulesByName;
} }
- (void)dealloc
{
[self invalidate];
}
#pragma mark - RCTInvalidating #pragma mark - RCTInvalidating
- (BOOL)isValid
{
return _javaScriptExecutor != nil;
}
- (void)invalidate - (void)invalidate
{ {
if (!self.isValid && _modulesByID == nil) { if (!self.isValid) {
return; return;
} }
if (![NSThread isMainThread]) { RCTAssertMainThread();
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
return;
}
[[NSNotificationCenter defaultCenter] removeObserver:self]; _valid = NO;
// Release executor
if (_latestJSExecutor == _javaScriptExecutor) { if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil; _latestJSExecutor = nil;
} }
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
[_displayLink invalidate]; /**
[_vsyncDisplayLink invalidate]; * Main Thread deallocations
_frameUpdateObservers = nil; */
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_mainDisplayLink invalidate];
// Invalidate modules [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
for (id target in _modulesByID.allObjects) { /**
if ([target respondsToSelector:@selector(invalidate)]) { * JS Thread deallocations
[(id<RCTInvalidating>)target invalidate]; */
[_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) // Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil; _javaScriptExecutor = nil;
_queuesByID = nil; _frameUpdateObservers = nil;
_modulesByName = nil; _modulesByID = nil;
_queuesByID = nil;
_modulesByName = nil;
}];
} }
/** /**
@ -1066,6 +1199,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/ */
- (void)_immediatelyCallTimer:(NSNumber *)timer - (void)_immediatelyCallTimer:(NSNumber *)timer
{ {
RCTAssertJSThread();
NSString *moduleDotMethod = @"RCTJSTimers.callTimers"; NSString *moduleDotMethod = @"RCTJSTimers.callTimers";
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod]; NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.", RCTAssert(moduleID != nil, @"Module '%@' not registered.",
@ -1074,35 +1209,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod]; NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod); RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (!_loading) { dispatch_block_t block = ^{
#if BATCHED_BRIDGE [self _actuallyInvokeAndProcessModule:@"BatchedBridge"
dispatch_block_t block = ^{ method:@"callFunctionReturnFlushedQueue"
[self _actuallyInvokeAndProcessModule:@"BatchedBridge" arguments:@[moduleID, methodID, @[@[timer]]]
method:@"callFunctionReturnFlushedQueue" context:RCTGetExecutorID(_javaScriptExecutor)];
arguments:@[moduleID, methodID, @[@[timer]]] };
context:RCTGetExecutorID(_javaScriptExecutor)];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
#else if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
[self _invokeAndProcessModule:@"BatchedBridge" } else {
method:@"callFunctionReturnFlushedQueue" [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
#endif
} }
} }
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{ {
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil"); RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginEvent(); RCTProfileBeginEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTAssertJSThread();
RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError); RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError);
if (scriptLoadError) { if (scriptLoadError) {
onComplete(scriptLoadError); onComplete(scriptLoadError);
@ -1132,7 +1261,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID - (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]) { if (queue == [NSNull null]) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else { } else {
@ -1146,13 +1281,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/ */
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context - (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:^{ [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileBeginEvent(); RCTProfileBeginEvent();
RCTBridge *strongSelf = weakSelf; RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) { if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) {
return; return;
} }
@ -1190,7 +1327,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/ */
if ( if (
[args[2][0] isEqual:callArgs[2][0]] && [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]; [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 - (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{ {
#endif RCTAssertJSThread();
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (!self.isValid) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self _handleBuffer:json context:context]; [self _handleBuffer:json context:context];
}; };
@ -1237,6 +1378,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context - (void)_handleBuffer:(id)buffer context:(NSNumber *)context
{ {
RCTAssertJSThread();
if (buffer == nil || buffer == (id)kCFNull) { if (buffer == nil || buffer == (id)kCFNull) {
return; return;
} }
@ -1307,6 +1450,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
params:(NSArray *)params params:(NSArray *)params
context:(NSNumber *)context context:(NSNumber *)context
{ {
RCTAssertJSThread();
if (!self.isValid) {
return NO;
}
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) { if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i); RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
@ -1330,10 +1478,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return NO; return NO;
} }
__weak RCTBridge *weakSelf = self; __weak RCTBatchedBridge *weakSelf = self;
[self dispatchBlock:^{ [self dispatchBlock:^{
RCTProfileBeginEvent(); RCTProfileBeginEvent();
__strong RCTBridge *strongSelf = weakSelf; RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) { if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this // strongSelf has been invalidated since the dispatch_async call and this
@ -1367,18 +1515,21 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink - (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{ {
RCTAssertJSThread();
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
RCTProfileBeginEvent(); RCTProfileBeginEvent();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) { for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) { 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]; NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor); NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) { calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
@ -1393,67 +1544,41 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
context:RCTGetExecutorID(_javaScriptExecutor)]; context:RCTGetExecutorID(_javaScriptExecutor)];
} }
#endif
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
} }
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink - (void)_mainThreadUpdate:(CADisplayLink *)displayLink
{ {
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); 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 - (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"); RCTLogError(@"To run the profiler you must be running from the dev server");
return; return;
} }
[_mainDisplayLink invalidate];
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
RCTProfileInit(); RCTProfileInit();
} }
- (void)stopProfiling - (void)stopProfiling
{ {
RCTAssertMainThread();
[_mainDisplayLink invalidate];
NSString *log = RCTProfileEnd(); 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]; NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
URLRequest.HTTPMethod = @"POST"; URLRequest.HTTPMethod = @"POST";

View File

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

View File

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

View File

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

View File

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

View File

@ -20,25 +20,37 @@
#import "RCTTouchHandler.h" #import "RCTTouchHandler.h"
#import "RCTUIManager.h" #import "RCTUIManager.h"
#import "RCTUtils.h" #import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWebViewExecutor.h" #import "RCTWebViewExecutor.h"
#import "UIView+React.h" #import "UIView+React.h"
@interface RCTBridge (RCTRootView)
@property (nonatomic, weak, readonly) RCTBridge *batchedBridge;
@end
@interface RCTUIManager (RCTRootView) @interface RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag; - (NSNumber *)allocateRootTag;
@end @end
@interface RCTRootContentView : RCTView <RCTInvalidating>
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge;
@end
@implementation RCTRootView @implementation RCTRootView
{ {
RCTBridge *_bridge; RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
NSString *_moduleName; NSString *_moduleName;
NSDictionary *_launchOptions; NSDictionary *_launchOptions;
UIView *_contentView; RCTRootContentView *_contentView;
} }
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName moduleName:(NSString *)moduleName
{ {
RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView"); RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
@ -52,11 +64,11 @@
_moduleName = moduleName; _moduleName = moduleName;
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading) selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification name:RCTJavaScriptDidLoadNotification
object:_bridge]; object:_bridge];
if (!_bridge.loading) { if (!_bridge.batchedBridge.isLoading) {
[self bundleFinishedLoading]; [self bundleFinishedLoading:_bridge.batchedBridge];
} }
} }
return self; return self;
@ -73,25 +85,6 @@
return [self initWithBridge:bridge moduleName:moduleName]; 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 - (UIViewController *)backingViewController
{ {
return _backingViewController ?: [super backingViewController]; return _backingViewController ?: [super backingViewController];
@ -105,9 +98,19 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication) RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) 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(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (!bridge.isValid) {
return;
}
/** /**
* Every root view that is created must have a unique React tag. * 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. * the React tag is assigned every time we load new content.
*/ */
[_contentView removeFromSuperview]; [_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds]; _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
_contentView.reactTag = [_bridge.uiManager allocateRootTag]; bridge:bridge];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_contentView addGestureRecognizer:_touchHandler];
[self addSubview:_contentView]; [self addSubview:_contentView];
NSString *moduleName = _moduleName ?: @""; NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{ NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag, @"rootTag": _contentView.reactTag,
@"initialProps": self.initialProperties ?: @{}, @"initialProps": _initialProperties ?: @{},
}; };
[_bridge.uiManager registerRootView:_contentView]; [bridge enqueueJSCall:@"AppRegistry.runApplication"
[_bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]]; args:@[moduleName, appParameters]];
}); });
} }
@ -139,7 +139,6 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
[super layoutSubviews]; [super layoutSubviews];
if (_contentView) { if (_contentView) {
_contentView.frame = self.bounds; _contentView.frame = self.bounds;
[_bridge.uiManager setFrame:self.bounds forRootView:_contentView];
} }
} }
@ -148,6 +147,12 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
return _contentView.reactTag; return _contentView.reactTag;
} }
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_contentView removeFromSuperview];
}
@end @end
@implementation RCTUIManager (RCTRootView) @implementation RCTUIManager (RCTRootView)
@ -160,3 +165,60 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
} }
@end @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 bridge = _bridge;
@synthesize paused = _paused;
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
@ -78,7 +79,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (instancetype)init - (instancetype)init
{ {
if ((self = [super init])) { if ((self = [super init])) {
_paused = YES;
_timers = [[RCTSparseArray alloc] init]; _timers = [[RCTSparseArray alloc] init];
for (NSString *name in @[UIApplicationWillResignActiveNotification, for (NSString *name in @[UIApplicationWillResignActiveNotification,
@ -126,7 +127,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (void)stopTimers - (void)stopTimers
{ {
[_bridge removeFrameUpdateObserver:self]; _paused = YES;
} }
- (void)startTimers - (void)startTimers
@ -135,7 +136,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
return; return;
} }
[_bridge addFrameUpdateObserver:self]; _paused = NO;
} }
- (void)didUpdateFrame:(RCTFrameUpdate *)update - (void)didUpdateFrame:(RCTFrameUpdate *)update

View File

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

View File

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