[ReactNative] Remove bridge retaining cycles

This commit is contained in:
Tadeu Zagallo
2015-04-10 07:28:10 -07:00
parent 4616037428
commit f5f80da80c
7 changed files with 110 additions and 59 deletions

View File

@@ -769,12 +769,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
object:self];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}];
}
}
@@ -834,7 +834,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
[self invalidate];
}
#pragma mark - RCTInvalidating
@@ -846,12 +846,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)invalidate
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (!self.isValid && _modulesByID == nil) {
return;
}
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
return;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Release executor
if (_latestJSExecutor == _javaScriptExecutor) {
@@ -860,11 +864,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
});
// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {

View File

@@ -29,7 +29,7 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add @synthesize bridge = _bridge;
*/
@property (nonatomic, strong) RCTBridge *bridge;
@property (nonatomic, weak) RCTBridge *bridge;
/**
* Place this macro in your class implementation, to automatically register

View File

@@ -24,7 +24,7 @@
*/
@implementation RCTJavaScriptLoader
{
RCTBridge *_bridge;
__weak RCTBridge *_bridge;
}
/**

View File

@@ -92,19 +92,6 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
- (void)setUp
{
if (!_registered) {
/**
* 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.
*/
_contentView = [[UIView alloc] init];
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_contentView addGestureRecognizer:_touchHandler];
[self addSubview:_contentView];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadViewsNotification
@@ -122,9 +109,9 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
- (void)tearDown
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_registered) {
_registered = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_contentView removeGestureRecognizer:_touchHandler];
[_contentView removeFromSuperview];
[_touchHandler invalidate];
@@ -169,6 +156,19 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
{
dispatch_async(dispatch_get_main_queue(), ^{
_registered = YES;
/**
* 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.
*/
_contentView = [[UIView alloc] initWithFrame:self.bounds];
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_contentView addGestureRecognizer:_touchHandler];
[self addSubview:_contentView];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,

View File

@@ -17,9 +17,45 @@
#import "RCTLog.h"
#import "RCTUtils.h"
@interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@property (nonatomic, assign, readonly) JSGlobalContextRef ctx;
- (instancetype)initWithJSContext:(JSGlobalContextRef)context;
@end
@implementation RCTJavaScriptContext
{
RCTJavaScriptContext *_self;
}
- (instancetype)initWithJSContext:(JSGlobalContextRef)context
{
if ((self = [super init])) {
_ctx = context;
_self = self;
}
return self;
}
- (BOOL)isValid
{
return _ctx != NULL;
}
- (void)invalidate
{
JSGlobalContextRelease(_ctx);
_ctx = NULL;
_self = nil;
}
@end
@implementation RCTContextExecutor
{
JSGlobalContextRef _context;
RCTJavaScriptContext *_context;
NSThread *_javaScriptThread;
}
@@ -129,21 +165,28 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
{
if ((self = [super init])) {
_javaScriptThread = javaScriptThread;
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue: ^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Assumes that no other JS tasks are scheduled before.
JSGlobalContextRef ctx;
if (context) {
_context = JSGlobalContextRetain(context);
ctx = JSGlobalContextRetain(context);
} else {
JSContextGroupRef group = JSContextGroupCreate();
_context = JSGlobalContextCreateInGroup(group, NULL);
ctx = JSGlobalContextCreateInGroup(group, NULL);
#if FB_JSC_HACK
JSContextGroupBindToCurrentThread(group);
#endif
JSContextGroupRelease(group);
}
[self _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[self _addNativeHook:RCTNoop withName:"noop"];
strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
[strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[strongSelf _addNativeHook:RCTNoop withName:"noop"];
}];
}
@@ -152,27 +195,24 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name
{
JSObjectRef globalObject = JSContextGetGlobalObject(_context);
JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);
JSStringRef JSName = JSStringCreateWithUTF8CString(name);
JSObjectSetProperty(_context, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context, JSName, hook), kJSPropertyAttributeNone, NULL);
JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL);
JSStringRelease(JSName);
}
- (BOOL)isValid
{
return _context != NULL;
return _context.isValid;
}
- (void)invalidate
{
if ([NSThread currentThread] != _javaScriptThread) {
// Yes, block until done. If we're getting called right before dealloc, it's the only safe option.
[self performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:YES];
} else if (_context != NULL) {
JSGlobalContextRelease(_context);
_context = NULL;
if (self.isValid) {
[_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO];
_context = nil;
}
}
@@ -187,7 +227,12 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.isValid) {
return;
}
NSError *error;
NSString *argsString = RCTJSONStringify(arguments, &error);
if (!argsString) {
@@ -199,11 +244,11 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)execString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, NULL, 0, &jsError);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, NULL, 0, &jsError);
JSStringRelease(execJSString);
if (!result) {
onComplete(nil, RCTNSErrorFromJSError(_context, jsError));
onComplete(nil, RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError));
return;
}
@@ -213,8 +258,8 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
id objcValue;
// We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
// to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
if (!JSValueIsNull(_context, result)) {
JSStringRef jsJSONString = JSValueCreateJSONString(_context, result, 0, nil);
if (!JSValueIsNull(strongSelf->_context.ctx, result)) {
JSStringRef jsJSONString = JSValueCreateJSONString(strongSelf->_context.ctx, result, 0, nil);
if (jsJSONString) {
NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString);
JSStringRelease(jsJSONString);
@@ -233,17 +278,22 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
{
RCTAssert(url != nil, @"url should not be nil");
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.isValid) {
return;
}
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, sourceURL, 0, &jsError);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, sourceURL, 0, &jsError);
JSStringRelease(sourceURL);
JSStringRelease(execJSString);
NSError *error;
if (!result) {
error = RCTNSErrorFromJSError(_context, jsError);
error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError);
}
onComplete(error);
@@ -269,9 +319,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
#endif
__weak RCTContextExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTContextExecutor *strongSelf = weakSelf;
if (!strongSelf || !strongSelf.isValid) {
return;
}
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSValueRef valueToInject = JSValueMakeFromJSONString(_context, execJSString);
JSValueRef valueToInject = JSValueMakeFromJSONString(strongSelf->_context.ctx, execJSString);
JSStringRelease(execJSString);
if (!valueToInject) {
@@ -283,10 +338,10 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
return;
}
JSObjectRef globalObject = JSContextGetGlobalObject(_context);
JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx);
JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName);
JSObjectSetProperty(_context, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
JSStringRelease(JSName);
onComplete(nil);
}];

View File

@@ -177,7 +177,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
@implementation RCTUIManager
{
dispatch_queue_t _shadowQueue;
__weak dispatch_queue_t _shadowQueue;
// Root views are only mutated on the shadow queue
NSMutableSet *_rootViewTags;
@@ -322,7 +322,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
// Register shadow view
dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
shadowView.frame = frame;
@@ -921,11 +920,9 @@ static void RCTMeasureLayout(RCTShadowView *view,
RCTResponseSenderBlock callback)
{
if (!view) {
RCTLogError(@"Attempting to measure view that does not exist");
return;
}
if (!ancestor) {
RCTLogError(@"Attempting to measure relative to ancestor that does not exist");
return;
}
CGRect result = [view measureLayoutRelativeToAncestor:ancestor];

View File

@@ -28,7 +28,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* allowing the manager (or the views that it manages) to manipulate the view
* hierarchy and send events back to the JS context.
*/
@property (nonatomic, strong) RCTBridge *bridge;
@property (nonatomic, weak) RCTBridge *bridge;
/**
* This method instantiates a native view to be managed by the module. Override